From 41425a893ddb27bd1b5a307e39c4762826b7258c Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Sun, 12 Jul 2020 13:30:08 +0200 Subject: [PATCH 01/48] Add monoids and semirings --- .../src/org/ejml/GenerateJavaCode32.java | 8 +++++ .../src/org/ejml/ops/DoubleMonoid.java | 34 +++++++++++++++++++ .../src/org/ejml/ops/DoubleSemiRing.java | 29 ++++++++++++++++ .../org/ejml/ops/PreDefinedDoubleMonoids.java | 32 +++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 main/ejml-core/src/org/ejml/ops/DoubleMonoid.java create mode 100644 main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java create mode 100644 main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java diff --git a/main/autocode/src/org/ejml/GenerateJavaCode32.java b/main/autocode/src/org/ejml/GenerateJavaCode32.java index 41c29cc40..4a6e18b5e 100644 --- a/main/autocode/src/org/ejml/GenerateJavaCode32.java +++ b/main/autocode/src/org/ejml/GenerateJavaCode32.java @@ -54,6 +54,12 @@ public GenerateJavaCode32() { prefix32.add("FUnary"); prefix64.add("DBinary"); prefix32.add("FBinary"); + prefix64.add("DoubleMonoid"); + prefix32.add("FloatMonoid"); + prefix64.add("DoubleSemiRing"); + prefix32.add("FloatSemiRing"); + prefix64.add("PreDefinedDoubleMonoids"); + prefix32.add("PreDefinedFloatMonoids"); prefix64.add("DScalar"); prefix32.add("FScalar"); prefix64.add("DMatrix"); @@ -93,6 +99,8 @@ public GenerateJavaCode32() { converter.replacePattern("DScalar", "FScalar"); converter.replacePattern("DUnary", "FUnary"); converter.replacePattern("DBinary", "FBinary"); + converter.replacePattern("DMonoid", "FMonoid"); + converter.replacePattern("DSemiRing", "FSemiRing"); converter.replacePattern("ConvertD", "ConvertF"); converter.replacePattern("DGrowArray", "FGrowArray"); converter.replacePattern("DMatrix", "FMatrix"); diff --git a/main/ejml-core/src/org/ejml/ops/DoubleMonoid.java b/main/ejml-core/src/org/ejml/ops/DoubleMonoid.java new file mode 100644 index 000000000..5df962dde --- /dev/null +++ b/main/ejml-core/src/org/ejml/ops/DoubleMonoid.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.ops; + +public class DoubleMonoid { + // possible re-interpreting 0 + public final double id; + public final DoubleBinaryOperator func; + + DoubleMonoid(double id, DoubleBinaryOperator func) { + this.id = id; + this.func = func; + } + + DoubleMonoid(DoubleBinaryOperator func) { + this(0, func); + } +} \ No newline at end of file diff --git a/main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java b/main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java new file mode 100644 index 000000000..c2a8d4d74 --- /dev/null +++ b/main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.ops; + +public class DoubleSemiRing { + public final DoubleMonoid add; + public final DoubleMonoid mult; + + DoubleSemiRing(DoubleMonoid add, DoubleMonoid mult) { + this.add = add; + this.mult = mult; + } +} \ No newline at end of file diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java new file mode 100644 index 000000000..e012ffea3 --- /dev/null +++ b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.ops; + +public final class PreDefinedDoubleMonoids { + public static final DoubleMonoid AND = new DoubleMonoid(1, (a, b) -> (a != 0 || b != 0) ? 0 : 1); + public static final DoubleMonoid OR = new DoubleMonoid(0, (a, b) -> (a != 0 && b != 0) ? 0 : 1); + public static final DoubleMonoid XOR = new DoubleMonoid(0, (a, b) -> ((a == 0 || b == 0) && (a == 1 || b == 1)) ? 0 : 1); + + public static final DoubleMonoid PLUS = new DoubleMonoid(0, Double::sum); + public static final DoubleMonoid MULT = new DoubleMonoid(1, (a, b) -> a * b); + + // TODO: performance incr. worth not using safe Math.min/max? + public final static DoubleMonoid MIN = new DoubleMonoid(Double.MAX_VALUE, (a, b) -> (a <= b) ? a : b); + public final static DoubleMonoid MAX = new DoubleMonoid(Double.MIN_VALUE, (a, b) -> (a >= b) ? a : b); +} \ No newline at end of file From df4b3965ec09e8f5839f54856ee4a362fe565f86 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Sun, 12 Jul 2020 20:24:38 +0200 Subject: [PATCH 02/48] Init sparse matrix ops using semirings .. as proposed in GraphBLAS --- .../csc/CommonOpsWithSemiRing_DSCC.java | 424 ++++++++++++++++++ .../misc/ImplCommonOpsWithSemiRing_DSCC.java | 183 ++++++++ ...ImplSparseSparseMultWithSemiRing_DSCC.java | 354 +++++++++++++++ .../MatrixVectorMultWithSemiRing_DSCC.java | 140 ++++++ 4 files changed, 1101 insertions(+) create mode 100644 main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java create mode 100644 main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java create mode 100644 main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java create mode 100644 main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java new file mode 100644 index 000000000..2ebf6b6b0 --- /dev/null +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc; + +import org.ejml.MatrixDimensionException; +import org.ejml.data.DGrowArray; +import org.ejml.data.DMatrixRMaj; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; +import org.ejml.ops.DMonoid; +import org.ejml.ops.DSemiRing; +import org.ejml.ops.DUnaryOperator; +import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; +import org.ejml.sparse.csc.mult.ImplSparseSparseMultWithSemiRing_DSCC; + +import javax.annotation.Nullable; +import java.util.Arrays; + +import static org.ejml.UtilEjml.stringShapes; + + +public class CommonOpsWithSemiRing_DSCC { + + public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing) { + mult(A, B, C, semiRing, null, null); + } + + /** + * Performs matrix multiplication. C = A*B + * + * @param A (Input) Matrix. Not modified. + * @param B (Input) Matrix. Not modified. + * @param C (Output) Storage for results. Data length is increased if increased if insufficient. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + if (A.numCols != B.numRows) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numRows, B.numCols); + + ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, C, semiRing, gw, gx); + } + + public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + if (A.numRows != B.numRows) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numCols, B.numCols); + + ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, C, semiRing, gw, gx); + } + + /** + * Performs matrix multiplication. C = A*BT. B needs to be sorted and will be sorted if it + * has not already been sorted. + * + * @param A (Input) Matrix. Not modified. + * @param B (Input) Matrix. Value not modified but indicies will be sorted if not sorted already. + * @param C (Output) Storage for results. Data length is increased if increased if insufficient. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + if (A.numCols != B.numCols) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numRows, B.numRows); + + if (!B.isIndicesSorted()) + B.sortIndices(null); + + ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, C, semiRing, gw, gx); + } + + + /** + * Performs matrix multiplication. C = A*B + * + * @param A Matrix + * @param B Dense Matrix + * @param C Dense Matrix + */ + public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + if (A.numCols != B.numRows) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numRows, B.numCols); + + ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, C, semiRing); + } + + /** + *

C = C + A*B

+ */ + public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + if (A.numRows != C.numRows || B.numCols != C.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + + ImplSparseSparseMultWithSemiRing_DSCC.multAdd(A, B, C, semiRing); + } + + /** + * Performs matrix multiplication. C = AT*B + * + * @param A Matrix + * @param B Dense Matrix + * @param C Dense Matrix + */ + public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + if (A.numRows != B.numRows) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numCols, B.numCols); + + ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, C, semiRing); + } + + /** + *

C = C + AT*B

+ */ + public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + if (A.numCols != C.numRows || B.numCols != C.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + + ImplSparseSparseMultWithSemiRing_DSCC.multAddTransA(A, B, C, semiRing); + } + + /** + * Performs matrix multiplication. C = A*BT + * + * @param A Matrix + * @param B Dense Matrix + * @param C Dense Matrix + */ + public static void multTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + // todo: combine with multAdd as only difference is that C is filled with zero before + if (A.numCols != B.numCols) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numRows, B.numRows); + + ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, C, semiRing); + } + + /** + *

C = C + A*BT

+ */ + public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + if (A.numRows != C.numRows || B.numRows != C.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + + // TODO: ? this is basically the equivalent of graphblas mult with specified accumulator op + ImplSparseSparseMultWithSemiRing_DSCC.multAddTransB(A, B, C, semiRing); + } + + /** + * Performs matrix multiplication. C = AT*BT + * + * @param A Matrix + * @param B Dense Matrix + * @param C Dense Matrix + */ + public static void multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + if (A.numRows != B.numCols) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numCols, B.numRows); + + ImplSparseSparseMultWithSemiRing_DSCC.multTransAB(A, B, C, semiRing); + } + + + /** + *

C = C + AT*BT

+ */ + public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + // TODO: this is basically the equivalent of graphblas mult with specified accumulator op + if (A.numCols != C.numRows || B.numRows != C.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + + ImplSparseSparseMultWithSemiRing_DSCC.multAddTransAB(A, B, C, semiRing); + } + + /** + * Performs matrix addition:
+ * C = αA + βB + * + * @param alpha scalar value multiplied against A + * @param A Matrix + * @param beta scalar value multiplied against B + * @param B Matrix + * @param C Output matrix. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + if (A.numRows != B.numRows || A.numCols != B.numCols) + throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + C.reshape(A.numRows, A.numCols); + + ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, C, semiRing, gw, gx); + } + + /** + * Performs an element-wise multiplication.
+ * C[i,j] = A[i,j]*B[i,j]
+ * All matrices must have the same shape. + * + * @param A (Input) Matrix. + * @param B (Input) Matrix + * @param C (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + if (A.numCols != B.numCols || A.numRows != B.numRows) + throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); + C.reshape(A.numRows, A.numCols); + + ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, C, semiRing, gw, gx); + } + + /** + * Applies the row permutation specified by the vector to the input matrix and save the results + * in the output matrix. output[perm[j],:] = input[j,:] + * + * @param permInv (Input) Inverse permutation vector. Specifies new order of the rows. + * @param input (Input) Matrix which is to be permuted + * @param output (Output) Matrix which has the permutation stored in it. Is reshaped. + */ + public static void permuteRowInv(int permInv[], DMatrixSparseCSC input, DMatrixSparseCSC output) { + CommonOps_DSCC.permuteRowInv(permInv, input, output); + } + + /** + * Applies the forward column and inverse row permutation specified by the two vector to the input matrix + * and save the results in the output matrix. output[permRow[j],permCol[i]] = input[j,i] + * + * @param permRowInv (Input) Inverse row permutation vector. Null is the same as passing in identity. + * @param input (Input) Matrix which is to be permuted + * @param permCol (Input) Column permutation vector. Null is the same as passing in identity. + * @param output (Output) Matrix which has the permutation stored in it. Is reshaped. + */ + public static void permute(@Nullable int permRowInv[], DMatrixSparseCSC input, @Nullable int permCol[], DMatrixSparseCSC output) { + CommonOps_DSCC.permute(permRowInv, input, permCol, output); + } + + /** + * Extracts a column from A and stores it into out. + * + * @param A (Input) Source matrix. not modified. + * @param column The column in A + * @param out (Output, Optional) Storage for column vector + * @return The column of A. + */ + public static DMatrixSparseCSC extractColumn(DMatrixSparseCSC A, int column, @Nullable DMatrixSparseCSC out) { + return extractColumn(A, column, out); + } + + /** + * Creates a submatrix by extracting the specified rows from A. rows = {row0 %le; i %le; row1}. + * + * @param A (Input) matrix + * @param row0 First row. Inclusive + * @param row1 Last row+1. + * @param out (Output, Option) Storage for output matrix + * @return The submatrix + */ + public static DMatrixSparseCSC extractRows(DMatrixSparseCSC A, int row0, int row1, DMatrixSparseCSC out) { + return CommonOps_DSCC.extractRows(A, row0, row1, out); + } + + /** + *

+ * Extracts a submatrix from 'src' and inserts it in a submatrix in 'dst'. + *

+ *

+ * si-y0 , j-x0 = oij for all y0 ≤ i < y1 and x0 ≤ j < x1
+ *
+ * where 'sij' is an element in the submatrix and 'oij' is an element in the + * original matrix. + *

+ * + *

WARNING: This is a very slow operation for sparse matrices. The current implementation is simple but + * involves excessive memory copying.

+ * + * @param src The original matrix which is to be copied. Not modified. + * @param srcX0 Start column. + * @param srcX1 Stop column+1. + * @param srcY0 Start row. + * @param srcY1 Stop row+1. + * @param dst Where the submatrix are stored. Modified. + * @param dstY0 Start row in dst. + * @param dstX0 start column in dst. + */ + public static void extract(DMatrixSparseCSC src, int srcY0, int srcY1, int srcX0, int srcX1, + DMatrixSparseCSC dst, int dstY0, int dstX0) { + CommonOps_DSCC.extract(src, srcY0, srcY1, srcX0, srcX1, dst, dstY0, dstX0); + } + + /** + * This applies a given unary function on every value stored in the matrix + * + * @param input (Input) input matrix. Not modified + * @param func Unary function accepting a double + * @param output (Input/Output) Matrix. Modified. + */ + public static void apply(DMatrixSparseCSC input, DUnaryOperator func, @Nullable DMatrixSparseCSC output) { + if (output == null) { + output = input.createLike(); + } else if (input != output) { + output.copyStructure(input); + } + + for (int i = 0; i < input.nz_values.length; i++) { + output.nz_values[i] = func.apply(input.nz_values[i]); + } + } + + public static void apply(DMatrixSparseCSC input, DUnaryOperator func) { + apply(input, func, input); + } + + /** + * This accumulates the matrix values to a scalar value + * + * @param input (Input) input matrix. Not modified + * @param initValue initial value for accumulator + * @param monoid Monoid defining "+" for accumulator += cellValue + * @return accumulated value + */ + public static double reduceScalar(DMatrixSparseCSC input, double initValue, DMonoid monoid) { + double result = initValue; + + for (int i = 0; i < input.nz_values.length; i++) { + result = monoid.func.apply(result, input.nz_values[i]); + } + + return result; + } + + public static double reduceScalar(DMatrixSparseCSC input, DMonoid monoid) { + return reduceScalar(input, 0, monoid); + } + + /** + * This accumulates the values per column to a scalar value + * + * @param input (Input) input matrix. Not modified + * @param initValue initial value for accumulator + * @param monoid Monoid defining "+" for accumulator += cellValue + * @param output output (Output) Vector, where result can be stored in + * @return a column-vector, where v[i] == values of column i reduced to scalar based on `func` + */ + public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initValue, DMonoid monoid, @Nullable DMatrixRMaj output) { + if (output == null) { + output = new DMatrixRMaj(1, input.numCols); + } else { + output.reshape(1, input.numCols); + } + + for (int col = 0; col < input.numCols; col++) { + int start = input.col_idx[col]; + int end = input.col_idx[col + 1]; + + double acc = initValue; + for (int i = start; i < end; i++) { + acc = monoid.func.apply(acc, input.nz_values[i]); + } + + // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) + output.data[col] = acc; + } + + return output; + } + + /** + * This accumulates the values per row to a scalar value + * + * @param input (Input) input matrix. Not modified + * @param initValue initial value for accumulator + * @param monoid Monoid defining "+" for accumulator += cellValue + * @param output output (Output) Vector, where result can be stored in + * @return a row-vector, where v[i] == values of row i reduced to scalar based on `func` + */ + public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue, DMonoid monoid, @Nullable DMatrixRMaj output) { + if (output == null) { + output = new DMatrixRMaj(1, input.numRows); + } else { + output.reshape(1, input.numCols); + } + // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) + Arrays.fill(output.data, initValue); + + for (int col = 0; col < input.numCols; col++) { + int start = input.col_idx[col]; + int end = input.col_idx[col + 1]; + + for (int i = start; i < end; i++) { + output.data[input.nz_rows[i]] = monoid.func.apply(output.data[input.nz_rows[i]], input.nz_values[i]); + } + } + + return output; + } +} + diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java new file mode 100644 index 000000000..03ed514b1 --- /dev/null +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.misc; + +import org.ejml.data.DGrowArray; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; +import org.ejml.ops.DoubleSemiRing; + +import javax.annotation.Nullable; +import java.util.Arrays; + +import static org.ejml.UtilEjml.adjust; +import static org.ejml.sparse.csc.mult.ImplSparseSparseMultWithSemiRing_DSCC.multAddColA; + +/** + * based on ImplCommonOps_DSCC + */ +public class ImplCommonOpsWithSemiRing_DSCC { + + /** + * Performs a matrix transpose. + * + * @param A Original matrix. Not modified. + * @param C Storage for transposed 'a'. Reshaped. + * @param gw (Optional) Storage for internal workspace. Can be null. + */ + public static void transpose(DMatrixSparseCSC A, DMatrixSparseCSC C, @Nullable IGrowArray gw) { + ImplCommonOps_DSCC.transpose(A, C, gw); + } + + /** + * Performs matrix addition:
+ * C = A + B + * + * @param A Matrix + * @param B Matrix + * @param C Output matrix. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows, A.numRows); + + C.indicesSorted = false; + C.nz_length = 0; + + for (int col = 0; col < A.numCols; col++) { + C.col_idx[col] = C.nz_length; + + multAddColA(A, col, alpha, C, col + 1, semiRing, x, w); + multAddColA(B, col, beta, C, col + 1, semiRing, x, w); + + // take the values in the dense vector 'x' and put them into 'C' + int idxC0 = C.col_idx[col]; + int idxC1 = C.col_idx[col + 1]; + + for (int i = idxC0; i < idxC1; i++) { + C.nz_values[i] = x[C.nz_rows[i]]; + } + } + C.col_idx[A.numCols] = C.nz_length; + } + + /** + * Adds the results of adding a column in A and B as a new column in C.
+ * C(:,end+1) = A(:,colA) + B(:,colB) + * + * @param A matrix + * @param colA column in A + * @param B matrix + * @param colB column in B + * @param C Column in C + * @param gw workspace + */ + public static void addColAppend(DMatrixSparseCSC A, int colA, DMatrixSparseCSC B, int colB, + DMatrixSparseCSC C, DoubleSemiRing semiRing, @Nullable IGrowArray gw) { + if (A.numRows != B.numRows || A.numRows != C.numRows) + throw new IllegalArgumentException("Number of rows in A, B, and C do not match"); + + int idxA0 = A.col_idx[colA]; + int idxA1 = A.col_idx[colA + 1]; + int idxB0 = B.col_idx[colB]; + int idxB1 = B.col_idx[colB + 1]; + + C.growMaxColumns(++C.numCols, true); + C.growMaxLength(C.nz_length + idxA1 - idxA0 + idxB1 - idxB0, true); + + int[] w = adjust(gw, A.numRows); + Arrays.fill(w, 0, A.numRows, -1); + + for (int i = idxA0; i < idxA1; i++) { + int row = A.nz_rows[i]; + C.nz_rows[C.nz_length] = row; + C.nz_values[C.nz_length] = A.nz_values[i]; + w[row] = C.nz_length++; + } + + for (int i = idxB0; i < idxB1; i++) { + int row = B.nz_rows[i]; + if (w[row] != -1) { + C.nz_values[w[row]] = semiRing.add.func.apply(C.nz_values[w[row]], B.nz_values[i]); + } else { + C.nz_values[C.nz_length] = B.nz_values[i]; + C.nz_rows[C.nz_length++] = row; + } + } + C.col_idx[C.numCols] = C.nz_length; + } + + /** + * Performs element-wise multiplication:
+ * C_ij = A_ij * B_ij + * + * @param A (Input) Matrix + * @param B (Input) Matrix + * @param C (Output) Matrix. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows); + Arrays.fill(w, 0, A.numRows, -1); // fill with -1. This will be a value less than column + + C.growMaxLength(Math.min(A.nz_length, B.nz_length), false); + C.indicesSorted = false; // Hmm I think if B is storted then C will be sorted... + C.nz_length = 0; + + for (int col = 0; col < A.numCols; col++) { + int idxA0 = A.col_idx[col]; + int idxA1 = A.col_idx[col + 1]; + int idxB0 = B.col_idx[col]; + int idxB1 = B.col_idx[col + 1]; + + // compute the maximum number of elements that there can be in this row + int maxInRow = Math.min(idxA1 - idxA0, idxB1 - idxB0); + + // make sure there are enough non-zero elements in C + if (C.nz_length + maxInRow > C.nz_values.length) + C.growMaxLength(C.nz_values.length + maxInRow, true); + + // update the structure of C + C.col_idx[col] = C.nz_length; + + // mark the rows that appear in A and save their value + for (int i = idxA0; i < idxA1; i++) { + int row = A.nz_rows[i]; + w[row] = col; + x[row] = A.nz_values[i]; + } + + // If a row appears in A and B, multiply and set as an element in C + for (int i = idxB0; i < idxB1; i++) { + int row = B.nz_rows[i]; + if (w[row] == col) { + C.nz_values[C.nz_length] = semiRing.mult.func.apply(x[row], B.nz_values[i]); + C.nz_rows[C.nz_length++] = row; + } + } + } + C.col_idx[C.numCols] = C.nz_length; + } +} \ No newline at end of file diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java new file mode 100644 index 000000000..7b243bdfa --- /dev/null +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2009-2019, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.mult; + +import org.ejml.data.DGrowArray; +import org.ejml.data.DMatrixRMaj; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; +import org.ejml.ops.DoubleSemiRing; +import org.ejml.sparse.csc.CommonOps_DSCC; + +import javax.annotation.Nullable; + +import static org.ejml.UtilEjml.adjust; + +/** + * based on ImplSparseSparseGraphMult_DSCC + */ +public class ImplSparseSparseMultWithSemiRing_DSCC { + + /** + * Performs matrix multiplication. C = A*B + * + * @param A Matrix + * @param B Matrix + * @param C Storage for results. Data length is increased if increased if insufficient. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows, A.numRows); + + C.growMaxLength(A.nz_length + B.nz_length, false); + C.indicesSorted = false; + C.nz_length = 0; + + // C(i,j) = sum_k A(i,k) * B(k,j) + int idx0 = B.col_idx[0]; + for (int bj = 1; bj <= B.numCols; bj++) { + int colB = bj - 1; + int idx1 = B.col_idx[bj]; + C.col_idx[bj] = C.nz_length; + + if (idx0 == idx1) { + continue; + } + + // C(:,j) = sum_k A(:,k)*B(k,j) + for (int bi = idx0; bi < idx1; bi++) { + int rowB = B.nz_rows[bi]; + double valB = B.nz_values[bi]; // B(k,j) k=rowB j=colB + + multAddColA(A, rowB, valB, C, colB + 1, semiRing, x, w); + } + + // take the values in the dense vector 'x' and put them into 'C' + int idxC0 = C.col_idx[colB]; + int idxC1 = C.col_idx[colB + 1]; + + for (int i = idxC0; i < idxC1; i++) { + C.nz_values[i] = x[C.nz_rows[i]]; + } + + idx0 = idx1; + } + + } + + /** + * Performs matrix multiplication. C = AT*B + * + * @param A Matrix + * @param B Matrix + * @param C Storage for results. Data length is increased if increased if insufficient. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows, A.numRows); + + C.growMaxLength(A.nz_length + B.nz_length, false); + C.indicesSorted = true; + C.nz_length = 0; + C.col_idx[0] = 0; + + int idxB0 = B.col_idx[0]; + for (int bj = 1; bj <= B.numCols; bj++) { + int idxB1 = B.col_idx[bj]; + C.col_idx[bj] = C.nz_length; + + if (idxB0 == idxB1) { + continue; + } + + // convert the column of B into a dense format and mark which rows are used + for (int bi = idxB0; bi < idxB1; bi++) { + int rowB = B.nz_rows[bi]; + x[rowB] = B.nz_values[bi]; + w[rowB] = bj; + } + + // C(colA,colB) = A(:,colA)*B(:,colB) + for (int colA = 0; colA < A.numCols; colA++) { + int idxA0 = A.col_idx[colA]; + int idxA1 = A.col_idx[colA + 1]; + + double sum = 0; + for (int ai = idxA0; ai < idxA1; ai++) { + int rowA = A.nz_rows[ai]; + if (w[rowA] == bj) { + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(x[rowA], A.nz_values[ai])); + } + } + + if (sum != 0) { + if (C.nz_length == C.nz_values.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } + C.nz_values[C.nz_length] = sum; + C.nz_rows[C.nz_length++] = colA; + } + } + C.col_idx[bj] = C.nz_length; + idxB0 = idxB1; + } + } + + /** + * Performs matrix multiplication. C = A*BT + * + * @param A Matrix + * @param B Matrix + * @param C Storage for results. Data length is increased if increased if insufficient. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + if (!B.isIndicesSorted()) + throw new IllegalArgumentException("B must have its indices sorted."); + else if (!CommonOps_DSCC.checkIndicesSorted(B)) { + throw new IllegalArgumentException("Crap. Not really sorted"); + } + + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows + B.numCols, A.numRows); + + C.growMaxLength(A.nz_length + B.nz_length, false); + C.indicesSorted = false; + C.nz_length = 0; + C.col_idx[0] = 0; + + // initialize w is the first index in each column of B + int locationB = A.numRows; + System.arraycopy(B.col_idx, 0, w, locationB, B.numCols); + + for (int colC = 0; colC < B.numRows; colC++) { + C.col_idx[colC + 1] = C.nz_length; // needs a value of B has nothing in the row + + // find the column in the transposed B + int mark = colC + 1; + for (int colB = 0; colB < B.numCols; colB++) { + int bi = w[locationB + colB]; + if (bi < B.col_idx[colB + 1]) { + int row = B.nz_rows[bi]; + if (row == colC) { + multAddColA(A, colB, B.nz_values[bi], C, mark, semiRing, x, w); + w[locationB + colB]++; + } + } + } + + // take the values in the dense vector 'x' and put them into 'C' + int idxC0 = C.col_idx[colC]; + int idxC1 = C.col_idx[colC + 1]; + + for (int i = idxC0; i < idxC1; i++) { + C.nz_values[i] = x[C.nz_rows[i]]; + } + } + } + + /** + * Performs the performing operation x = x + A(:,i)*alpha + * + *

NOTE: This is the same as cs_scatter() in csparse.

+ */ + public static void multAddColA(DMatrixSparseCSC A, int colA, + double alpha, + DMatrixSparseCSC C, int mark, + DoubleSemiRing semiRing, + double x[], int w[]) { + int idxA0 = A.col_idx[colA]; + int idxA1 = A.col_idx[colA + 1]; + + for (int j = idxA0; j < idxA1; j++) { + int row = A.nz_rows[j]; + + if (w[row] < mark) { + if (C.nz_length >= C.nz_rows.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } + + w[row] = mark; + C.nz_rows[C.nz_length] = row; + C.col_idx[mark] = ++C.nz_length; + x[row] = semiRing.mult.func.apply(A.nz_values[j], alpha); + } else { + x[row] = semiRing.add.func.apply(x[row], semiRing.mult.func.apply(A.nz_values[j], alpha)); + } + } + } + + public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + C.zero(); + multAdd(A, B, C, semiRing); + } + + public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + // C(i,j) = sum_k A(i,k) * B(k,j) + for (int k = 0; k < A.numCols; k++) { + int idx0 = A.col_idx[k]; + int idx1 = A.col_idx[k + 1]; + + for (int indexA = idx0; indexA < idx1; indexA++) { + int i = A.nz_rows[indexA]; + double valueA = A.nz_values[indexA]; + + int indexB = k * B.numCols; + int indexC = i * C.numCols; + int end = indexB + B.numCols; + +// for (int j = 0; j < B.numCols; j++) { + while (indexB < end) { + C.data[indexC++] = semiRing.add.func.apply(C.data[indexC++], semiRing.mult.func.apply(valueA, B.data[indexB++])); + } + } + } + } + + public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + + // C(i,j) = sum_k A(k,i) * B(k,j) + for (int j = 0; j < B.numCols; j++) { + + for (int i = 0; i < A.numCols; i++) { + int idx0 = A.col_idx[i]; + int idx1 = A.col_idx[i + 1]; + + double sum = 0; + for (int indexA = idx0; indexA < idx1; indexA++) { + int rowK = A.nz_rows[indexA]; + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(A.nz_values[indexA], B.data[rowK * B.numCols + j])); + } + + C.data[i * C.numCols + j] = sum; + } + } + } + + public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + // C(i,j) = sum_k A(k,i) * B(k,j) + for (int j = 0; j < B.numCols; j++) { + + for (int i = 0; i < A.numCols; i++) { + int idx0 = A.col_idx[i]; + int idx1 = A.col_idx[i + 1]; + + double sum = 0; + for (int indexA = idx0; indexA < idx1; indexA++) { + int rowK = A.nz_rows[indexA]; + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(A.nz_values[indexA], B.data[rowK * B.numCols + j])); + } + + C.data[i * C.numCols + j] = semiRing.add.func.apply(C.data[i * C.numCols + j], sum); + } + } + } + + public static void multTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + C.zero(); + multAddTransB(A, B, C, semiRing); + } + + public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + + // C(i,j) = sum_k A(i,k) * B(j,k) + for (int k = 0; k < A.numCols; k++) { + int idx0 = A.col_idx[k]; + int idx1 = A.col_idx[k + 1]; + for (int indexA = idx0; indexA < idx1; indexA++) { + for (int j = 0; j < B.numRows; j++) { + int i = A.nz_rows[indexA]; + C.data[i * C.numCols + j] = semiRing.add.func.apply( + C.data[i * C.numCols + j], + semiRing.mult.func.apply( + A.nz_values[indexA], + B.data[j * B.numCols + k] + ) + ); + } + } + } + } + + public static void multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + C.zero(); + multAddTransAB(A, B, C, semiRing); + } + + public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + // C(i,j) = sum_k A(k,i) * B(j,K) + for (int i = 0; i < A.numCols; i++) { + int idx0 = A.col_idx[i]; + int idx1 = A.col_idx[i + 1]; + + for (int indexA = idx0; indexA < idx1; indexA++) { + for (int j = 0; j < B.numRows; j++) { + int indexB = j * B.numCols; + + int k = A.nz_rows[indexA]; + + C.data[i * C.numCols + j] = semiRing.add.func.apply( + C.data[i * C.numCols + j], + semiRing.mult.func.apply( + A.nz_values[indexA], + B.data[indexB + k] + ) + ); + } + } + } + } +} \ No newline at end of file diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java new file mode 100644 index 000000000..60bb39363 --- /dev/null +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2009-2018, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.mult; + +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.ops.DoubleSemiRing; + +import java.util.Arrays; + +/** + * based on MartrixVectorMult_DSCC + */ +public class MatrixVectorMultWithSemiRing_DSCC { + /** + * c = A*b + * + * @param A (Input) Matrix + * @param b (Input) vector + * @param offsetB (Input) first index in vector b + * @param c (Output) vector + * @param offsetC (Output) first index in vector c + */ + public static void mult(DMatrixSparseCSC A , + double b[] , int offsetB , + double c[] , int offsetC, DoubleSemiRing semiRing) + { + Arrays.fill(c,offsetC,offsetC+A.numRows,0); + multAdd(A,b,offsetB,c,offsetC, semiRing); + } + + /** + * c = c + A*b + * @param A (Input) Matrix + * @param b (Input) vector + * @param offsetB (Input) first index in vector b + * @param c (Output) vector + * @param offsetC (Output) first index in vector c + * @param semiRing + */ + public static void multAdd(DMatrixSparseCSC A, + double[] b, int offsetB, + double[] c, int offsetC, DoubleSemiRing semiRing) + { + if( b.length-offsetB < A.numCols) + throw new IllegalArgumentException("Length of 'b' isn't long enough"); + if( c.length-offsetC < A.numRows) + throw new IllegalArgumentException("Length of 'c' isn't long enough"); + + for (int k = 0; k < A.numCols; k++) { + int idx0 = A.col_idx[k ]; + int idx1 = A.col_idx[k+1]; + + for (int indexA = idx0; indexA < idx1; indexA++) { + c[offsetC + A.nz_rows[indexA]] = semiRing.add.func.apply( + c[offsetC + A.nz_rows[indexA]], + semiRing.mult.func.apply(A.nz_values[indexA], b[offsetB + k])); + } + } + } + + /** + * c = aT*B + * + * @param a (Input) vector + * @param offsetA Input) first index in vector a + * @param B (Input) Matrix + * @param c (Output) vector + * @param offsetC (Output) first index in vector c + */ + public static void mult( double a[] , int offsetA , + DMatrixSparseCSC B , + double c[] , int offsetC, DoubleSemiRing semiRing ) + { + if( a.length-offsetA < B.numRows) + throw new IllegalArgumentException("Length of 'a' isn't long enough"); + if( c.length-offsetC < B.numCols) + throw new IllegalArgumentException("Length of 'c' isn't long enough"); + + for (int k = 0; k < B.numCols; k++) { + int idx0 = B.col_idx[k]; + int idx1 = B.col_idx[k + 1]; + + double sum = 0; + for (int indexB = idx0; indexB < idx1; indexB++) { + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(a[offsetA + B.nz_rows[indexB]], B.nz_values[indexB])); + } + c[offsetC + k] = sum; + } + } + + /** + * scalar = AT*B*C + * + * @param a (Input) vector + * @param offsetA Input) first index in vector a + * @param B (Input) Matrix + * @param c (Output) vector + * @param offsetC (Output) first index in vector c + */ + public static double innerProduct( double a[] , int offsetA , + DMatrixSparseCSC B , + double c[] , int offsetC, DoubleSemiRing semiRing) + { + if( a.length-offsetA < B.numRows) + throw new IllegalArgumentException("Length of 'a' isn't long enough"); + if( c.length-offsetC < B.numCols) + throw new IllegalArgumentException("Length of 'c' isn't long enough"); + + double output = 0; + + for (int k = 0; k < B.numCols; k++) { + int idx0 = B.col_idx[k ]; + int idx1 = B.col_idx[k+1]; + + double sum = 0; + for (int indexB = idx0; indexB < idx1; indexB++) { + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(a[offsetA + B.nz_rows[indexB]], B.nz_values[indexB])); + } + output = semiRing.add.func.apply(output, semiRing.mult.func.apply(sum, c[offsetC + k])); + } + + return output; + } +} From 61f5d57cdd8dc0569ac6a58b0acea443e98c74f0 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 13 Jul 2020 14:15:22 +0200 Subject: [PATCH 03/48] Test and fix predefined monoids --- .../org/ejml/ops/PreDefinedDoubleMonoids.java | 7 ++- .../org/ejml/ops/TestAlgebraicStructures.java | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java index e012ffea3..56db096c4 100644 --- a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java +++ b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java @@ -19,9 +19,10 @@ package org.ejml.ops; public final class PreDefinedDoubleMonoids { - public static final DoubleMonoid AND = new DoubleMonoid(1, (a, b) -> (a != 0 || b != 0) ? 0 : 1); - public static final DoubleMonoid OR = new DoubleMonoid(0, (a, b) -> (a != 0 && b != 0) ? 0 : 1); - public static final DoubleMonoid XOR = new DoubleMonoid(0, (a, b) -> ((a == 0 || b == 0) && (a == 1 || b == 1)) ? 0 : 1); + public static final DoubleMonoid AND = new DoubleMonoid(1, (a, b) -> (a == 0 || b == 0) ? 0 : 1); + public static final DoubleMonoid OR = new DoubleMonoid(0, (a, b) -> (a != 0 || b != 0) ? 1 : 0); + public static final DoubleMonoid XOR = new DoubleMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 0 : 1); + public static final DoubleMonoid XNOR = new DoubleMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 1 : 0); public static final DoubleMonoid PLUS = new DoubleMonoid(0, Double::sum); public static final DoubleMonoid MULT = new DoubleMonoid(1, (a, b) -> a * b); diff --git a/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java b/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java new file mode 100644 index 000000000..54b22de2c --- /dev/null +++ b/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.ops; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestAlgebraicStructures { + + @Test + public void testPreDefinedMonoids() { + assertEquals(0, PreDefinedDoubleMonoids.AND.func.apply(20, 0)); + assertEquals(0, PreDefinedDoubleMonoids.AND.func.apply(0, 20)); + assertEquals(0, PreDefinedDoubleMonoids.AND.func.apply(0, 0)); + assertEquals(1, PreDefinedDoubleMonoids.AND.func.apply(43, 20)); + + assertEquals(1, PreDefinedDoubleMonoids.OR.func.apply(43, 20)); + assertEquals(1, PreDefinedDoubleMonoids.OR.func.apply(0, 20)); + assertEquals(1, PreDefinedDoubleMonoids.OR.func.apply(43, 0)); + assertEquals(0, PreDefinedDoubleMonoids.OR.func.apply(0, 0)); + + assertEquals(0, PreDefinedDoubleMonoids.XOR.func.apply(43, 20)); + assertEquals(1, PreDefinedDoubleMonoids.XOR.func.apply(0, 20)); + assertEquals(1, PreDefinedDoubleMonoids.XOR.func.apply(43, 0)); + assertEquals(0, PreDefinedDoubleMonoids.XOR.func.apply(0, 0)); + + assertEquals(1, PreDefinedDoubleMonoids.XNOR.func.apply(43, 20)); + assertEquals(0, PreDefinedDoubleMonoids.XNOR.func.apply(0, 20)); + assertEquals(0, PreDefinedDoubleMonoids.XNOR.func.apply(43, 0)); + assertEquals(1, PreDefinedDoubleMonoids.XNOR.func.apply(0, 0)); + + assertEquals(43, PreDefinedDoubleMonoids.MAX.func.apply(43, 20)); + assertEquals(20, PreDefinedDoubleMonoids.MIN.func.apply(43, 20)); + + + assertEquals(63, PreDefinedDoubleMonoids.PLUS.func.apply(43, 20)); + assertEquals(860, PreDefinedDoubleMonoids.MULT.func.apply(43, 20)); + } +} \ No newline at end of file From bcbc2b635776e4900d85d903a99a83772c93a7a0 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 13 Jul 2020 21:42:39 +0200 Subject: [PATCH 04/48] Add predefined semi-rings .. and rename mult to times (as in graphblas) --- .../src/org/ejml/GenerateJavaCode32.java | 2 + .../org/ejml/ops/PreDefinedDoubleMonoids.java | 6 ++- .../ejml/ops/PreDefinedDoubleSemiRings.java | 42 +++++++++++++++++++ .../org/ejml/ops/TestAlgebraicStructures.java | 2 +- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java diff --git a/main/autocode/src/org/ejml/GenerateJavaCode32.java b/main/autocode/src/org/ejml/GenerateJavaCode32.java index 4a6e18b5e..fa8e98cfb 100644 --- a/main/autocode/src/org/ejml/GenerateJavaCode32.java +++ b/main/autocode/src/org/ejml/GenerateJavaCode32.java @@ -60,6 +60,8 @@ public GenerateJavaCode32() { prefix32.add("FloatSemiRing"); prefix64.add("PreDefinedDoubleMonoids"); prefix32.add("PreDefinedFloatMonoids"); + prefix64.add("PreDefinedDoubleSemiRings"); + prefix32.add("PreDefinedFloatSemiRings"); prefix64.add("DScalar"); prefix32.add("FScalar"); prefix64.add("DMatrix"); diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java index 56db096c4..fc51c1eae 100644 --- a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java +++ b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java @@ -18,6 +18,10 @@ package org.ejml.ops; +/** + * as defined in the graphblas c-api (https://people.eecs.berkeley.edu/~aydin/GraphBLAS_API_C_v13.pdf) + * p. 26 + */ public final class PreDefinedDoubleMonoids { public static final DoubleMonoid AND = new DoubleMonoid(1, (a, b) -> (a == 0 || b == 0) ? 0 : 1); public static final DoubleMonoid OR = new DoubleMonoid(0, (a, b) -> (a != 0 || b != 0) ? 1 : 0); @@ -25,7 +29,7 @@ public final class PreDefinedDoubleMonoids { public static final DoubleMonoid XNOR = new DoubleMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 1 : 0); public static final DoubleMonoid PLUS = new DoubleMonoid(0, Double::sum); - public static final DoubleMonoid MULT = new DoubleMonoid(1, (a, b) -> a * b); + public static final DoubleMonoid TIMES = new DoubleMonoid(1, (a, b) -> a * b); // TODO: performance incr. worth not using safe Math.min/max? public final static DoubleMonoid MIN = new DoubleMonoid(Double.MAX_VALUE, (a, b) -> (a <= b) ? a : b); diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java new file mode 100644 index 000000000..9c667c4e8 --- /dev/null +++ b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.ops; + +import static org.ejml.ops.PreDefinedDoubleMonoids.*; + +/** + * as defined in the graphblas c-api (https://people.eecs.berkeley.edu/~aydin/GraphBLAS_API_C_v13.pdf) + * p. 27-28 + * Note: some don’t have a multiplicative annihilator (thus no "true" semi-rings") + */ +public final class PreDefinedDoubleSemiRings { + public static final DoubleSemiRing PLUS_TIMES = new DoubleSemiRing(PLUS, TIMES); + public static final DoubleSemiRing MIN_PLUS = new DoubleSemiRing(MIN, PLUS); + public static final DoubleSemiRing MAX_PLUS = new DoubleSemiRing(MAX, PLUS); + public static final DoubleSemiRing MIN_TIMES = new DoubleSemiRing(MIN, TIMES); + public static final DoubleSemiRing MIN_MAX = new DoubleSemiRing(MIN, MAX); + public static final DoubleSemiRing MAX_MIN = new DoubleSemiRing(MAX, MIN); + public static final DoubleSemiRing MAX_TIMES = new DoubleSemiRing(MAX, TIMES); + public static final DoubleSemiRing PLUS_MIN = new DoubleSemiRing(PLUS, MIN); + + public static final DoubleSemiRing OR_AND = new DoubleSemiRing(OR, AND); + public static final DoubleSemiRing AND_OR = new DoubleSemiRing(AND, OR); + public static final DoubleSemiRing XOR_AND = new DoubleSemiRing(XOR, AND); + public static final DoubleSemiRing XNOR_OR = new DoubleSemiRing(XNOR, OR); +} \ No newline at end of file diff --git a/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java b/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java index 54b22de2c..ea59ffab8 100644 --- a/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java +++ b/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java @@ -52,6 +52,6 @@ public void testPreDefinedMonoids() { assertEquals(63, PreDefinedDoubleMonoids.PLUS.func.apply(43, 20)); - assertEquals(860, PreDefinedDoubleMonoids.MULT.func.apply(43, 20)); + assertEquals(860, PreDefinedDoubleMonoids.TIMES.func.apply(43, 20)); } } \ No newline at end of file From b553f6744fa10cdb594e9279696e901249aa8baa Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 14 Jul 2020 18:18:25 +0200 Subject: [PATCH 05/48] Add dependency for parameterized tests --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 97dabe968..d0bfb7bd5 100644 --- a/build.gradle +++ b/build.gradle @@ -83,6 +83,7 @@ subprojects { compile 'com.google.code.findbugs:jsr305:3.0.2' // @Nullable testImplementation( 'org.junit.jupiter:junit-jupiter-api:5.4.0' ) + testImplementation( 'org.junit.jupiter:junit-jupiter-params:5.4.0' ) testRuntimeOnly( 'org.junit.jupiter:junit-jupiter-engine:5.4.0' ) ['core','generator-annprocess'].each { String a-> From 2ef0cc3bfa0ac025ae8d21db5a968b0eadb517e4 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 14 Jul 2020 18:20:26 +0200 Subject: [PATCH 06/48] Test and Fix Matrix x Vector with semi-rings .. fixing errors based on wrong identity element --- .../MatrixVectorMultWithSemiRing_DSCC.java | 91 +++++++------ ...TestMatrixVectorMultWithSemiRing_DSCC.java | 124 ++++++++++++++++++ 2 files changed, 172 insertions(+), 43 deletions(-) create mode 100644 main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java index 60bb39363..46342c5d3 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java @@ -30,41 +30,44 @@ public class MatrixVectorMultWithSemiRing_DSCC { /** * c = A*b * - * @param A (Input) Matrix - * @param b (Input) vector + * @param A (Input) Matrix + * @param b (Input) vector * @param offsetB (Input) first index in vector b - * @param c (Output) vector + * @param c (Output) vector * @param offsetC (Output) first index in vector c */ - public static void mult(DMatrixSparseCSC A , - double b[] , int offsetB , - double c[] , int offsetC, DoubleSemiRing semiRing) - { - Arrays.fill(c,offsetC,offsetC+A.numRows,0); - multAdd(A,b,offsetB,c,offsetC, semiRing); + public static void mult(DMatrixSparseCSC A, + double b[], int offsetB, + double c[], int offsetC, DoubleSemiRing semiRing) { + Arrays.fill(c, offsetC, offsetC + A.numRows, semiRing.add.id); + multAdd(A, b, offsetB, c, offsetC, semiRing); + } + + public static void mult(DMatrixSparseCSC A, double b[], double c[], DoubleSemiRing semiRing) { + mult(A, b, 0, c, 0, semiRing); } /** * c = c + A*b - * @param A (Input) Matrix - * @param b (Input) vector - * @param offsetB (Input) first index in vector b - * @param c (Output) vector - * @param offsetC (Output) first index in vector c + * + * @param A (Input) Matrix + * @param b (Input) vector + * @param offsetB (Input) first index in vector b + * @param c (Output) vector + * @param offsetC (Output) first index in vector c * @param semiRing */ public static void multAdd(DMatrixSparseCSC A, double[] b, int offsetB, - double[] c, int offsetC, DoubleSemiRing semiRing) - { - if( b.length-offsetB < A.numCols) + double[] c, int offsetC, DoubleSemiRing semiRing) { + if (b.length - offsetB < A.numCols) throw new IllegalArgumentException("Length of 'b' isn't long enough"); - if( c.length-offsetC < A.numRows) + if (c.length - offsetC < A.numRows) throw new IllegalArgumentException("Length of 'c' isn't long enough"); for (int k = 0; k < A.numCols; k++) { - int idx0 = A.col_idx[k ]; - int idx1 = A.col_idx[k+1]; + int idx0 = A.col_idx[k]; + int idx1 = A.col_idx[k + 1]; for (int indexA = idx0; indexA < idx1; indexA++) { c[offsetC + A.nz_rows[indexA]] = semiRing.add.func.apply( @@ -77,26 +80,25 @@ public static void multAdd(DMatrixSparseCSC A, /** * c = aT*B * - * @param a (Input) vector - * @param offsetA Input) first index in vector a - * @param B (Input) Matrix - * @param c (Output) vector + * @param a (Input) vector + * @param offsetA Input) first index in vector a + * @param B (Input) Matrix + * @param c (Output) vector * @param offsetC (Output) first index in vector c */ - public static void mult( double a[] , int offsetA , - DMatrixSparseCSC B , - double c[] , int offsetC, DoubleSemiRing semiRing ) - { - if( a.length-offsetA < B.numRows) + public static void mult(double a[], int offsetA, + DMatrixSparseCSC B, + double c[], int offsetC, DoubleSemiRing semiRing) { + if (a.length - offsetA < B.numRows) throw new IllegalArgumentException("Length of 'a' isn't long enough"); - if( c.length-offsetC < B.numCols) + if (c.length - offsetC < B.numCols) throw new IllegalArgumentException("Length of 'c' isn't long enough"); for (int k = 0; k < B.numCols; k++) { int idx0 = B.col_idx[k]; int idx1 = B.col_idx[k + 1]; - double sum = 0; + double sum = semiRing.add.id; for (int indexB = idx0; indexB < idx1; indexB++) { sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(a[offsetA + B.nz_rows[indexB]], B.nz_values[indexB])); } @@ -104,29 +106,32 @@ public static void mult( double a[] , int offsetA , } } + public static void mult(double a[], DMatrixSparseCSC B, double c[], DoubleSemiRing semiRing) { + mult(a, 0, B, c, 0, semiRing); + } + /** * scalar = AT*B*C * - * @param a (Input) vector - * @param offsetA Input) first index in vector a - * @param B (Input) Matrix - * @param c (Output) vector + * @param a (Input) vector + * @param offsetA Input) first index in vector a + * @param B (Input) Matrix + * @param c (Output) vector * @param offsetC (Output) first index in vector c */ - public static double innerProduct( double a[] , int offsetA , - DMatrixSparseCSC B , - double c[] , int offsetC, DoubleSemiRing semiRing) - { - if( a.length-offsetA < B.numRows) + public static double innerProduct(double a[], int offsetA, + DMatrixSparseCSC B, + double c[], int offsetC, DoubleSemiRing semiRing) { + if (a.length - offsetA < B.numRows) throw new IllegalArgumentException("Length of 'a' isn't long enough"); - if( c.length-offsetC < B.numCols) + if (c.length - offsetC < B.numCols) throw new IllegalArgumentException("Length of 'c' isn't long enough"); double output = 0; for (int k = 0; k < B.numCols; k++) { - int idx0 = B.col_idx[k ]; - int idx1 = B.col_idx[k+1]; + int idx0 = B.col_idx[k]; + int idx1 = B.col_idx[k + 1]; double sum = 0; for (int indexB = idx0; indexB < idx1; indexB++) { diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java new file mode 100644 index 000000000..95a048a1e --- /dev/null +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.mult; + +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.ops.DoubleSemiRing; +import org.ejml.ops.PreDefinedDoubleSemiRings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestMatrixVectorMultWithSemiRing_DSCC { + DMatrixSparseCSC inputMatrix; + + @BeforeEach + public void setUp() { + // based on example in http://mit.bme.hu/~szarnyas/grb/graphblas-introduction.pdf + inputMatrix = new DMatrixSparseCSC(7, 7, 12); + inputMatrix.set(0, 1, 1); + inputMatrix.set(0, 3, 1); + inputMatrix.set(1, 4, 1); + inputMatrix.set(1, 6, 1); + inputMatrix.set(2, 5, 1); + inputMatrix.set(3, 0, .2); + inputMatrix.set(3, 2, .4); + inputMatrix.set(4, 5, 1); + inputMatrix.set(5, 2, .5); + inputMatrix.set(6, 2, 1); + inputMatrix.set(6, 3, 1); + inputMatrix.set(6, 4, 1); + + } + + @ParameterizedTest(name = "{0}") + @MethodSource("vectorMatrixMultSources") + public void mult_v_A(String desc, DoubleSemiRing semiRing, double[] expected) { + // graphblas == following outgoing edges of source nodes + double v[] = new double[7]; + Arrays.fill(v, semiRing.add.id); + v[3] = 0.5; + v[5] = 0.6; + + System.out.println("input vector = " + Arrays.toString(v)); + + //input.print(); + + double found[] = new double[7]; + + MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, found, semiRing); + + System.out.println("found = " + Arrays.toString(found)); + System.out.println("expected = " + Arrays.toString(expected)); + assertTrue(Arrays.equals(found, expected)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("matrixVectorMultSources") + public void mult_A_v(String desc, DoubleSemiRing semiRing, double[] expected) { + // graphblas == following incoming edges of source nodes + double v[] = new double[7]; + Arrays.fill(v, semiRing.add.id); + v[3] = 0.5; + v[4] = 0.6; + + System.out.println("input vector = " + Arrays.toString(v)); + + //input.print(); + + double found[] = new double[7]; + + MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, found, semiRing); + + System.out.println("found = " + Arrays.toString(found)); + System.out.println("expected = " + Arrays.toString(expected)); + assertTrue(Arrays.equals(found, expected)); + } + + private static Stream vectorMatrixMultSources() { + return Stream.of( + Arguments.of("Plus, Times", PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{0.1, 0, 0.5, 0, 0, 0, 0}), + Arguments.of("OR, AND", PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 0, 1, 0, 0, 0, 0}), + Arguments.of("MIN, PLUS", PreDefinedDoubleSemiRings.MIN_PLUS, + new double[]{0.7, Double.MAX_VALUE, 0.9, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE}), +// This would only work on sparse vectors (here resulting in 1.0 instead of Double.MIN_Value) +// Arguments.of("MAX, PLUS", PreDefinedDoubleSemiRings.MAX_PLUS, +// new double[]{0.7, Double.MIN_VALUE, 1.1, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE}), + Arguments.of("MIN, TIMES", PreDefinedDoubleSemiRings.MIN_TIMES, + new double[]{0.1, Double.MAX_VALUE, 0.2, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE}), + Arguments.of("MAX, MIN", PreDefinedDoubleSemiRings.MAX_MIN, + new double[]{0.2, Double.MIN_VALUE, 0.5, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE}) + ); + } + + private static Stream matrixVectorMultSources() { + return Stream.of( + Arguments.of("Plus, Times", PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{.5, .6, 0, 0, 0, 0, 1.1}), + Arguments.of("OR, AND", PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1, 0, 0, 0, 0, 1}), + Arguments.of("MIN, PLUS", PreDefinedDoubleSemiRings.MIN_PLUS, + new double[]{1.5, 1.6, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, 1.5}) + ); + } +} \ No newline at end of file From d20b382f52ddfaacef70c16540df26d1b1700519 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 14 Jul 2020 19:53:58 +0200 Subject: [PATCH 07/48] Test elementWiseMult and add --- .../TestImplCommonOpsWithSemiRing_DSCC.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java new file mode 100644 index 000000000..5f582ebff --- /dev/null +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.misc; + +import org.apache.commons.math3.util.Pair; +import org.ejml.EjmlUnitTests; +import org.ejml.UtilEjml; +import org.ejml.data.DMatrixSparse; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; +import org.ejml.ops.DoubleSemiRing; +import org.ejml.ops.PreDefinedDoubleSemiRings; +import org.ejml.sparse.csc.CommonOps_DSCC; +import org.ejml.sparse.csc.MatrixFeatures_DSCC; +import org.ejml.sparse.csc.RandomMatrices_DSCC; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Random; +import java.util.Set; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestImplCommonOpsWithSemiRing_DSCC { + + @ParameterizedTest + @MethodSource("elementWiseAddSource") + public void add(DoubleSemiRing semiRing, double[] expected) { + // == graph unions + DMatrixSparseCSC a = new DMatrixSparseCSC(3, 3); + DMatrixSparseCSC b = a.copy(); + DMatrixSparseCSC c = a.copy(); + + a.set(1, 1, 2); + + b.set(1, 1, 3); + b.set(0, 0, 4); + + ImplCommonOpsWithSemiRing_DSCC.add(1, a, 1, b, c, semiRing, null, null); + + double[] found = new double[]{c.get(0, 0), c.get(1, 1)}; + assertTrue(c.getNumElements() == 2); + + assertTrue(Arrays.equals(expected, found)); + } + + @ParameterizedTest + @MethodSource("elementWiseMultSource") + public void testelementWiseMult(DoubleSemiRing semiRing, double[] expected) { + // == graph intersection + DMatrixSparseCSC matrix = new DMatrixSparseCSC(3, 3, 4); + matrix.set(1, 1, 4); + matrix.set(1, 2, -2); + + DMatrixSparseCSC otherMatrix = matrix.copy(); + otherMatrix.set(1, 1, 3); + otherMatrix.set(1, 2, 1); + + + matrix.set(0, 2, 1); + otherMatrix.set(2, 0, 1); + + DMatrixSparseCSC result = new DMatrixSparseCSC(3, 3, 0); + + ImplCommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, result, semiRing, null, null); + + assertEquals(2, result.getNumElements()); + assertTrue(expected[0] == result.get(1, 1)); + assertTrue(expected[1] == result.get(1, 2)); + } + + private static Stream elementWiseAddSource() { + return Stream.of( + Arguments.of(PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{4, 5}), + Arguments.of(PreDefinedDoubleSemiRings.MIN_MAX, new double[]{4, 2}), + Arguments.of(PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1}) + ); + } + + private static Stream elementWiseMultSource() { + return Stream.of( + Arguments.of(PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{12, -2}), + Arguments.of(PreDefinedDoubleSemiRings.PLUS_MIN, new double[]{3, -2}), + Arguments.of(PreDefinedDoubleSemiRings.MIN_MAX, new double[]{4, 1}), + Arguments.of(PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1}) + ); + } +} From dc710e5b4203b80805d54c78239aff89d50ea942 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 14 Jul 2020 20:10:29 +0200 Subject: [PATCH 08/48] Initialise result of matrix-matrix mult-ops with id of plus operator instead of 0 --- main/ejml-core/src/org/ejml/data/DMatrixRMaj.java | 7 +++++++ .../mult/ImplSparseSparseMultWithSemiRing_DSCC.java | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/main/ejml-core/src/org/ejml/data/DMatrixRMaj.java b/main/ejml-core/src/org/ejml/data/DMatrixRMaj.java index 4ee6c9d15..c0891fc00 100644 --- a/main/ejml-core/src/org/ejml/data/DMatrixRMaj.java +++ b/main/ejml-core/src/org/ejml/data/DMatrixRMaj.java @@ -333,6 +333,13 @@ public void zero() { Arrays.fill(data, 0, getNumElements(), 0.0); } + /** + * Sets all elements equal to the specified value. + */ + public void fill(double value) { + Arrays.fill(data, 0, getNumElements(), value); + } + /** * Creates and returns a matrix which is idential to this one. * diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 7b243bdfa..2d533fa8d 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -124,7 +124,7 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSpa int idxA0 = A.col_idx[colA]; int idxA1 = A.col_idx[colA + 1]; - double sum = 0; + double sum = semiRing.add.id; for (int ai = idxA0; ai < idxA1; ai++) { int rowA = A.nz_rows[ai]; if (w[rowA] == bj) { @@ -132,7 +132,7 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSpa } } - if (sum != 0) { + if (sum != semiRing.add.id) { if (C.nz_length == C.nz_values.length) { C.growMaxLength(C.nz_length * 2 + 1, true); } @@ -232,7 +232,7 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, } public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { - C.zero(); + C.fill(semiRing.add.id); multAdd(A, B, C, semiRing); } @@ -267,7 +267,7 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, int idx0 = A.col_idx[i]; int idx1 = A.col_idx[i + 1]; - double sum = 0; + double sum = semiRing.add.id; for (int indexA = idx0; indexA < idx1; indexA++) { int rowK = A.nz_rows[indexA]; sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(A.nz_values[indexA], B.data[rowK * B.numCols + j])); @@ -286,7 +286,7 @@ public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj int idx0 = A.col_idx[i]; int idx1 = A.col_idx[i + 1]; - double sum = 0; + double sum = semiRing.add.id; for (int indexA = idx0; indexA < idx1; indexA++) { int rowK = A.nz_rows[indexA]; sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(A.nz_values[indexA], B.data[rowK * B.numCols + j])); From f182fb702e518fd9b32ff064caa554a4a6ea10c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Wed, 15 Jul 2020 13:06:50 +0200 Subject: [PATCH 09/48] Clarify problem with "MIN-PLUS" semi-ring and dense vectors --- .../sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java index 95a048a1e..a1c2c428f 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java @@ -103,7 +103,7 @@ private static Stream vectorMatrixMultSources() { Arguments.of("OR, AND", PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 0, 1, 0, 0, 0, 0}), Arguments.of("MIN, PLUS", PreDefinedDoubleSemiRings.MIN_PLUS, new double[]{0.7, Double.MAX_VALUE, 0.9, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE}), -// This would only work on sparse vectors (here resulting in 1.0 instead of Double.MIN_Value) +// This only works on sparse input vectors (here resulting in 1.0 instead of Double.MIN_Value as max(1.0, Double.MIN_VALUE) = 1.0) // Arguments.of("MAX, PLUS", PreDefinedDoubleSemiRings.MAX_PLUS, // new double[]{0.7, Double.MIN_VALUE, 1.1, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE}), Arguments.of("MIN, TIMES", PreDefinedDoubleSemiRings.MIN_TIMES, From 94ad9cf4de6d93d6ad670c21e785e408f07524a4 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 16 Jul 2020 16:32:21 +0200 Subject: [PATCH 10/48] Fix warning about lossy conversion from double to float --- .../csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java index a1c2c428f..f5c0c2490 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java @@ -43,10 +43,10 @@ public void setUp() { inputMatrix.set(1, 4, 1); inputMatrix.set(1, 6, 1); inputMatrix.set(2, 5, 1); - inputMatrix.set(3, 0, .2); - inputMatrix.set(3, 2, .4); + inputMatrix.set(3, 0, 0.2); + inputMatrix.set(3, 2, 0.4); inputMatrix.set(4, 5, 1); - inputMatrix.set(5, 2, .5); + inputMatrix.set(5, 2, 0.5); inputMatrix.set(6, 2, 1); inputMatrix.set(6, 3, 1); inputMatrix.set(6, 4, 1); @@ -115,7 +115,7 @@ private static Stream vectorMatrixMultSources() { private static Stream matrixVectorMultSources() { return Stream.of( - Arguments.of("Plus, Times", PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{.5, .6, 0, 0, 0, 0, 1.1}), + Arguments.of("Plus, Times", PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{0.5, 0.6, 0, 0, 0, 0, 1.1}), Arguments.of("OR, AND", PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1, 0, 0, 0, 0, 1}), Arguments.of("MIN, PLUS", PreDefinedDoubleSemiRings.MIN_PLUS, new double[]{1.5, 1.6, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, 1.5}) From f98197f7313697b1cbc5a92fe69243fa91df0ca5 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 20 Jul 2020 18:24:22 +0200 Subject: [PATCH 11/48] Add semi-rings with first and second for mul operation --- .../src/org/ejml/ops/PreDefinedDoubleSemiRings.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java index 9c667c4e8..63b5dd011 100644 --- a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java +++ b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java @@ -39,4 +39,14 @@ public final class PreDefinedDoubleSemiRings { public static final DoubleSemiRing AND_OR = new DoubleSemiRing(AND, OR); public static final DoubleSemiRing XOR_AND = new DoubleSemiRing(XOR, AND); public static final DoubleSemiRing XNOR_OR = new DoubleSemiRing(XNOR, OR); + + // only private as they have no identity element, hence can only be used for add + private static final DoubleMonoid FIRST = new DoubleMonoid(Double.NaN, (x, y) -> x); + private static final DoubleMonoid SECOND = new DoubleMonoid(Double.NaN, (x, y) -> y); + + // semi-rings with no multiplicative annihilator + public static final DoubleSemiRing MIN_FIRST = new DoubleSemiRing(MIN, FIRST); + public static final DoubleSemiRing MIN_SECOND = new DoubleSemiRing(MIN, SECOND); + public static final DoubleSemiRing MAX_FIRST = new DoubleSemiRing(MAX, FIRST); + public static final DoubleSemiRing MAX_SECOND = new DoubleSemiRing(MAX, SECOND); } \ No newline at end of file From 6f5571b5ae0516c5a40442d68d197851728d8c17 Mon Sep 17 00:00:00 2001 From: Florentin Doerre Date: Tue, 28 Jul 2020 14:28:43 +0200 Subject: [PATCH 12/48] Use D and F instead of Double and Float for Semi-rings and Monoids --- .../src/org/ejml/GenerateJavaCode32.java | 16 +++--- .../ops/{DoubleMonoid.java => DMonoid.java} | 8 +-- ...efinedDoubleMonoids.java => DMonoids.java} | 18 +++---- .../{DoubleSemiRing.java => DSemiRing.java} | 8 +-- .../src/org/ejml/ops/DSemiRings.java | 52 +++++++++++++++++++ .../ejml/ops/PreDefinedDoubleSemiRings.java | 52 ------------------- .../org/ejml/ops/TestAlgebraicStructures.java | 42 +++++++-------- .../misc/ImplCommonOpsWithSemiRing_DSCC.java | 8 +-- ...ImplSparseSparseMultWithSemiRing_DSCC.java | 26 +++++----- .../MatrixVectorMultWithSemiRing_DSCC.java | 14 ++--- .../TestImplCommonOpsWithSemiRing_DSCC.java | 34 ++++-------- ...TestMatrixVectorMultWithSemiRing_DSCC.java | 24 ++++----- 12 files changed, 145 insertions(+), 157 deletions(-) rename main/ejml-core/src/org/ejml/ops/{DoubleMonoid.java => DMonoid.java} (83%) rename main/ejml-core/src/org/ejml/ops/{PreDefinedDoubleMonoids.java => DMonoids.java} (50%) rename main/ejml-core/src/org/ejml/ops/{DoubleSemiRing.java => DSemiRing.java} (82%) create mode 100644 main/ejml-core/src/org/ejml/ops/DSemiRings.java delete mode 100644 main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java diff --git a/main/autocode/src/org/ejml/GenerateJavaCode32.java b/main/autocode/src/org/ejml/GenerateJavaCode32.java index fa8e98cfb..8330776f8 100644 --- a/main/autocode/src/org/ejml/GenerateJavaCode32.java +++ b/main/autocode/src/org/ejml/GenerateJavaCode32.java @@ -54,14 +54,14 @@ public GenerateJavaCode32() { prefix32.add("FUnary"); prefix64.add("DBinary"); prefix32.add("FBinary"); - prefix64.add("DoubleMonoid"); - prefix32.add("FloatMonoid"); - prefix64.add("DoubleSemiRing"); - prefix32.add("FloatSemiRing"); - prefix64.add("PreDefinedDoubleMonoids"); - prefix32.add("PreDefinedFloatMonoids"); - prefix64.add("PreDefinedDoubleSemiRings"); - prefix32.add("PreDefinedFloatSemiRings"); + prefix64.add("DMonoid"); + prefix32.add("FMonoid"); + prefix64.add("DSemiRing"); + prefix32.add("FSemiRing"); + prefix64.add("DMonoids"); + prefix32.add("FMonoids"); + prefix64.add("DSemiRings"); + prefix32.add("FSemiRings"); prefix64.add("DScalar"); prefix32.add("FScalar"); prefix64.add("DMatrix"); diff --git a/main/ejml-core/src/org/ejml/ops/DoubleMonoid.java b/main/ejml-core/src/org/ejml/ops/DMonoid.java similarity index 83% rename from main/ejml-core/src/org/ejml/ops/DoubleMonoid.java rename to main/ejml-core/src/org/ejml/ops/DMonoid.java index 5df962dde..417111626 100644 --- a/main/ejml-core/src/org/ejml/ops/DoubleMonoid.java +++ b/main/ejml-core/src/org/ejml/ops/DMonoid.java @@ -18,17 +18,17 @@ package org.ejml.ops; -public class DoubleMonoid { +public class DMonoid { // possible re-interpreting 0 public final double id; - public final DoubleBinaryOperator func; + public final DBinaryOperator func; - DoubleMonoid(double id, DoubleBinaryOperator func) { + DMonoid(double id, DBinaryOperator func) { this.id = id; this.func = func; } - DoubleMonoid(DoubleBinaryOperator func) { + DMonoid(DBinaryOperator func) { this(0, func); } } \ No newline at end of file diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java b/main/ejml-core/src/org/ejml/ops/DMonoids.java similarity index 50% rename from main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java rename to main/ejml-core/src/org/ejml/ops/DMonoids.java index fc51c1eae..cbd83bc1a 100644 --- a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleMonoids.java +++ b/main/ejml-core/src/org/ejml/ops/DMonoids.java @@ -22,16 +22,16 @@ * as defined in the graphblas c-api (https://people.eecs.berkeley.edu/~aydin/GraphBLAS_API_C_v13.pdf) * p. 26 */ -public final class PreDefinedDoubleMonoids { - public static final DoubleMonoid AND = new DoubleMonoid(1, (a, b) -> (a == 0 || b == 0) ? 0 : 1); - public static final DoubleMonoid OR = new DoubleMonoid(0, (a, b) -> (a != 0 || b != 0) ? 1 : 0); - public static final DoubleMonoid XOR = new DoubleMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 0 : 1); - public static final DoubleMonoid XNOR = new DoubleMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 1 : 0); +public final class DMonoids { + public static final DMonoid AND = new DMonoid(1, (a, b) -> (a == 0 || b == 0) ? 0 : 1); + public static final DMonoid OR = new DMonoid(0, (a, b) -> (a != 0 || b != 0) ? 1 : 0); + public static final DMonoid XOR = new DMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 0 : 1); + public static final DMonoid XNOR = new DMonoid(0, (a, b) -> ((a == 0 && b == 0) || (a != 0 && b != 0)) ? 1 : 0); - public static final DoubleMonoid PLUS = new DoubleMonoid(0, Double::sum); - public static final DoubleMonoid TIMES = new DoubleMonoid(1, (a, b) -> a * b); + public static final DMonoid PLUS = new DMonoid(0, Double::sum); + public static final DMonoid TIMES = new DMonoid(1, (a, b) -> a * b); // TODO: performance incr. worth not using safe Math.min/max? - public final static DoubleMonoid MIN = new DoubleMonoid(Double.MAX_VALUE, (a, b) -> (a <= b) ? a : b); - public final static DoubleMonoid MAX = new DoubleMonoid(Double.MIN_VALUE, (a, b) -> (a >= b) ? a : b); + public final static DMonoid MIN = new DMonoid(Double.MAX_VALUE, (a, b) -> (a <= b) ? a : b); + public final static DMonoid MAX = new DMonoid(Double.MIN_VALUE, (a, b) -> (a >= b) ? a : b); } \ No newline at end of file diff --git a/main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java b/main/ejml-core/src/org/ejml/ops/DSemiRing.java similarity index 82% rename from main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java rename to main/ejml-core/src/org/ejml/ops/DSemiRing.java index c2a8d4d74..2720b7fd7 100644 --- a/main/ejml-core/src/org/ejml/ops/DoubleSemiRing.java +++ b/main/ejml-core/src/org/ejml/ops/DSemiRing.java @@ -18,11 +18,11 @@ package org.ejml.ops; -public class DoubleSemiRing { - public final DoubleMonoid add; - public final DoubleMonoid mult; +public class DSemiRing { + public final DMonoid add; + public final DMonoid mult; - DoubleSemiRing(DoubleMonoid add, DoubleMonoid mult) { + DSemiRing(DMonoid add, DMonoid mult) { this.add = add; this.mult = mult; } diff --git a/main/ejml-core/src/org/ejml/ops/DSemiRings.java b/main/ejml-core/src/org/ejml/ops/DSemiRings.java new file mode 100644 index 000000000..d5d29ff75 --- /dev/null +++ b/main/ejml-core/src/org/ejml/ops/DSemiRings.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.ops; + +import static org.ejml.ops.DMonoids.*; + +/** + * as defined in the graphblas c-api (https://people.eecs.berkeley.edu/~aydin/GraphBLAS_API_C_v13.pdf) + * p. 27-28 + * Note: some don’t have a multiplicative annihilator (thus no "true" semi-rings") + */ +public final class DSemiRings { + public static final DSemiRing PLUS_TIMES = new DSemiRing(PLUS, TIMES); + public static final DSemiRing MIN_PLUS = new DSemiRing(MIN, PLUS); + public static final DSemiRing MAX_PLUS = new DSemiRing(MAX, PLUS); + public static final DSemiRing MIN_TIMES = new DSemiRing(MIN, TIMES); + public static final DSemiRing MIN_MAX = new DSemiRing(MIN, MAX); + public static final DSemiRing MAX_MIN = new DSemiRing(MAX, MIN); + public static final DSemiRing MAX_TIMES = new DSemiRing(MAX, TIMES); + public static final DSemiRing PLUS_MIN = new DSemiRing(PLUS, MIN); + + public static final DSemiRing OR_AND = new DSemiRing(OR, AND); + public static final DSemiRing AND_OR = new DSemiRing(AND, OR); + public static final DSemiRing XOR_AND = new DSemiRing(XOR, AND); + public static final DSemiRing XNOR_OR = new DSemiRing(XNOR, OR); + + // only private as they have no identity element, hence can only be used for add + private static final DMonoid FIRST = new DMonoid(Double.NaN, (x, y) -> x); + private static final DMonoid SECOND = new DMonoid(Double.NaN, (x, y) -> y); + + // semi-rings with no multiplicative annihilator + public static final DSemiRing MIN_FIRST = new DSemiRing(MIN, FIRST); + public static final DSemiRing MIN_SECOND = new DSemiRing(MIN, SECOND); + public static final DSemiRing MAX_FIRST = new DSemiRing(MAX, FIRST); + public static final DSemiRing MAX_SECOND = new DSemiRing(MAX, SECOND); +} \ No newline at end of file diff --git a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java b/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java deleted file mode 100644 index 63b5dd011..000000000 --- a/main/ejml-core/src/org/ejml/ops/PreDefinedDoubleSemiRings.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. - * - * This file is part of Efficient Java Matrix Library (EJML). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.ejml.ops; - -import static org.ejml.ops.PreDefinedDoubleMonoids.*; - -/** - * as defined in the graphblas c-api (https://people.eecs.berkeley.edu/~aydin/GraphBLAS_API_C_v13.pdf) - * p. 27-28 - * Note: some don’t have a multiplicative annihilator (thus no "true" semi-rings") - */ -public final class PreDefinedDoubleSemiRings { - public static final DoubleSemiRing PLUS_TIMES = new DoubleSemiRing(PLUS, TIMES); - public static final DoubleSemiRing MIN_PLUS = new DoubleSemiRing(MIN, PLUS); - public static final DoubleSemiRing MAX_PLUS = new DoubleSemiRing(MAX, PLUS); - public static final DoubleSemiRing MIN_TIMES = new DoubleSemiRing(MIN, TIMES); - public static final DoubleSemiRing MIN_MAX = new DoubleSemiRing(MIN, MAX); - public static final DoubleSemiRing MAX_MIN = new DoubleSemiRing(MAX, MIN); - public static final DoubleSemiRing MAX_TIMES = new DoubleSemiRing(MAX, TIMES); - public static final DoubleSemiRing PLUS_MIN = new DoubleSemiRing(PLUS, MIN); - - public static final DoubleSemiRing OR_AND = new DoubleSemiRing(OR, AND); - public static final DoubleSemiRing AND_OR = new DoubleSemiRing(AND, OR); - public static final DoubleSemiRing XOR_AND = new DoubleSemiRing(XOR, AND); - public static final DoubleSemiRing XNOR_OR = new DoubleSemiRing(XNOR, OR); - - // only private as they have no identity element, hence can only be used for add - private static final DoubleMonoid FIRST = new DoubleMonoid(Double.NaN, (x, y) -> x); - private static final DoubleMonoid SECOND = new DoubleMonoid(Double.NaN, (x, y) -> y); - - // semi-rings with no multiplicative annihilator - public static final DoubleSemiRing MIN_FIRST = new DoubleSemiRing(MIN, FIRST); - public static final DoubleSemiRing MIN_SECOND = new DoubleSemiRing(MIN, SECOND); - public static final DoubleSemiRing MAX_FIRST = new DoubleSemiRing(MAX, FIRST); - public static final DoubleSemiRing MAX_SECOND = new DoubleSemiRing(MAX, SECOND); -} \ No newline at end of file diff --git a/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java b/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java index ea59ffab8..6f8957b8e 100644 --- a/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java +++ b/main/ejml-core/test/org/ejml/ops/TestAlgebraicStructures.java @@ -21,37 +21,37 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; public class TestAlgebraicStructures { @Test public void testPreDefinedMonoids() { - assertEquals(0, PreDefinedDoubleMonoids.AND.func.apply(20, 0)); - assertEquals(0, PreDefinedDoubleMonoids.AND.func.apply(0, 20)); - assertEquals(0, PreDefinedDoubleMonoids.AND.func.apply(0, 0)); - assertEquals(1, PreDefinedDoubleMonoids.AND.func.apply(43, 20)); + assertEquals(0, DMonoids.AND.func.apply(20, 0)); + assertEquals(0, DMonoids.AND.func.apply(0, 20)); + assertEquals(0, DMonoids.AND.func.apply(0, 0)); + assertEquals(1, DMonoids.AND.func.apply(43, 20)); - assertEquals(1, PreDefinedDoubleMonoids.OR.func.apply(43, 20)); - assertEquals(1, PreDefinedDoubleMonoids.OR.func.apply(0, 20)); - assertEquals(1, PreDefinedDoubleMonoids.OR.func.apply(43, 0)); - assertEquals(0, PreDefinedDoubleMonoids.OR.func.apply(0, 0)); + assertEquals(1, DMonoids.OR.func.apply(43, 20)); + assertEquals(1, DMonoids.OR.func.apply(0, 20)); + assertEquals(1, DMonoids.OR.func.apply(43, 0)); + assertEquals(0, DMonoids.OR.func.apply(0, 0)); - assertEquals(0, PreDefinedDoubleMonoids.XOR.func.apply(43, 20)); - assertEquals(1, PreDefinedDoubleMonoids.XOR.func.apply(0, 20)); - assertEquals(1, PreDefinedDoubleMonoids.XOR.func.apply(43, 0)); - assertEquals(0, PreDefinedDoubleMonoids.XOR.func.apply(0, 0)); + assertEquals(0, DMonoids.XOR.func.apply(43, 20)); + assertEquals(1, DMonoids.XOR.func.apply(0, 20)); + assertEquals(1, DMonoids.XOR.func.apply(43, 0)); + assertEquals(0, DMonoids.XOR.func.apply(0, 0)); - assertEquals(1, PreDefinedDoubleMonoids.XNOR.func.apply(43, 20)); - assertEquals(0, PreDefinedDoubleMonoids.XNOR.func.apply(0, 20)); - assertEquals(0, PreDefinedDoubleMonoids.XNOR.func.apply(43, 0)); - assertEquals(1, PreDefinedDoubleMonoids.XNOR.func.apply(0, 0)); + assertEquals(1, DMonoids.XNOR.func.apply(43, 20)); + assertEquals(0, DMonoids.XNOR.func.apply(0, 20)); + assertEquals(0, DMonoids.XNOR.func.apply(43, 0)); + assertEquals(1, DMonoids.XNOR.func.apply(0, 0)); - assertEquals(43, PreDefinedDoubleMonoids.MAX.func.apply(43, 20)); - assertEquals(20, PreDefinedDoubleMonoids.MIN.func.apply(43, 20)); + assertEquals(43, DMonoids.MAX.func.apply(43, 20)); + assertEquals(20, DMonoids.MIN.func.apply(43, 20)); - assertEquals(63, PreDefinedDoubleMonoids.PLUS.func.apply(43, 20)); - assertEquals(860, PreDefinedDoubleMonoids.TIMES.func.apply(43, 20)); + assertEquals(63, DMonoids.PLUS.func.apply(43, 20)); + assertEquals(860, DMonoids.TIMES.func.apply(43, 20)); } } \ No newline at end of file diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index 03ed514b1..21c1fca8d 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -21,7 +21,7 @@ import org.ejml.data.DGrowArray; import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; -import org.ejml.ops.DoubleSemiRing; +import org.ejml.ops.DSemiRing; import javax.annotation.Nullable; import java.util.Arrays; @@ -55,7 +55,7 @@ public static void transpose(DMatrixSparseCSC A, DMatrixSparseCSC C, @Nullable I * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -92,7 +92,7 @@ public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSpa * @param gw workspace */ public static void addColAppend(DMatrixSparseCSC A, int colA, DMatrixSparseCSC B, int colB, - DMatrixSparseCSC C, DoubleSemiRing semiRing, @Nullable IGrowArray gw) { + DMatrixSparseCSC C, DSemiRing semiRing, @Nullable IGrowArray gw) { if (A.numRows != B.numRows || A.numRows != C.numRows) throw new IllegalArgumentException("Number of rows in A, B, and C do not match"); @@ -136,7 +136,7 @@ public static void addColAppend(DMatrixSparseCSC A, int colA, DMatrixSparseCSC B * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows); diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 2d533fa8d..4a46c727b 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -22,7 +22,7 @@ import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; -import org.ejml.ops.DoubleSemiRing; +import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.CommonOps_DSCC; import javax.annotation.Nullable; @@ -43,7 +43,7 @@ public class ImplSparseSparseMultWithSemiRing_DSCC { * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -93,7 +93,7 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -154,7 +154,7 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSpa * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DoubleSemiRing semiRing, + public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (!B.isIndicesSorted()) throw new IllegalArgumentException("B must have its indices sorted."); @@ -208,7 +208,7 @@ else if (!CommonOps_DSCC.checkIndicesSorted(B)) { public static void multAddColA(DMatrixSparseCSC A, int colA, double alpha, DMatrixSparseCSC C, int mark, - DoubleSemiRing semiRing, + DSemiRing semiRing, double x[], int w[]) { int idxA0 = A.col_idx[colA]; int idxA1 = A.col_idx[colA + 1]; @@ -231,12 +231,12 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, } } - public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { C.fill(semiRing.add.id); multAdd(A, B, C, semiRing); } - public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { // C(i,j) = sum_k A(i,k) * B(k,j) for (int k = 0; k < A.numCols; k++) { int idx0 = A.col_idx[k]; @@ -258,7 +258,7 @@ public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, Dou } } - public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { // C(i,j) = sum_k A(k,i) * B(k,j) for (int j = 0; j < B.numCols; j++) { @@ -278,7 +278,7 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, } } - public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { // C(i,j) = sum_k A(k,i) * B(k,j) for (int j = 0; j < B.numCols; j++) { @@ -297,12 +297,12 @@ public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj } } - public static void multTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { C.zero(); multAddTransB(A, B, C, semiRing); } - public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { // C(i,j) = sum_k A(i,k) * B(j,k) for (int k = 0; k < A.numCols; k++) { @@ -323,12 +323,12 @@ public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj } } - public static void multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { C.zero(); multAddTransAB(A, B, C, semiRing); } - public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DoubleSemiRing semiRing) { + public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { // C(i,j) = sum_k A(k,i) * B(j,K) for (int i = 0; i < A.numCols; i++) { int idx0 = A.col_idx[i]; diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java index 46342c5d3..eadd75bf5 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java @@ -19,7 +19,7 @@ package org.ejml.sparse.csc.mult; import org.ejml.data.DMatrixSparseCSC; -import org.ejml.ops.DoubleSemiRing; +import org.ejml.ops.DSemiRing; import java.util.Arrays; @@ -38,12 +38,12 @@ public class MatrixVectorMultWithSemiRing_DSCC { */ public static void mult(DMatrixSparseCSC A, double b[], int offsetB, - double c[], int offsetC, DoubleSemiRing semiRing) { + double c[], int offsetC, DSemiRing semiRing) { Arrays.fill(c, offsetC, offsetC + A.numRows, semiRing.add.id); multAdd(A, b, offsetB, c, offsetC, semiRing); } - public static void mult(DMatrixSparseCSC A, double b[], double c[], DoubleSemiRing semiRing) { + public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing semiRing) { mult(A, b, 0, c, 0, semiRing); } @@ -59,7 +59,7 @@ public static void mult(DMatrixSparseCSC A, double b[], double c[], DoubleSemiRi */ public static void multAdd(DMatrixSparseCSC A, double[] b, int offsetB, - double[] c, int offsetC, DoubleSemiRing semiRing) { + double[] c, int offsetC, DSemiRing semiRing) { if (b.length - offsetB < A.numCols) throw new IllegalArgumentException("Length of 'b' isn't long enough"); if (c.length - offsetC < A.numRows) @@ -88,7 +88,7 @@ public static void multAdd(DMatrixSparseCSC A, */ public static void mult(double a[], int offsetA, DMatrixSparseCSC B, - double c[], int offsetC, DoubleSemiRing semiRing) { + double c[], int offsetC, DSemiRing semiRing) { if (a.length - offsetA < B.numRows) throw new IllegalArgumentException("Length of 'a' isn't long enough"); if (c.length - offsetC < B.numCols) @@ -106,7 +106,7 @@ public static void mult(double a[], int offsetA, } } - public static void mult(double a[], DMatrixSparseCSC B, double c[], DoubleSemiRing semiRing) { + public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing) { mult(a, 0, B, c, 0, semiRing); } @@ -121,7 +121,7 @@ public static void mult(double a[], DMatrixSparseCSC B, double c[], DoubleSemiRi */ public static double innerProduct(double a[], int offsetA, DMatrixSparseCSC B, - double c[], int offsetC, DoubleSemiRing semiRing) { + double c[], int offsetC, DSemiRing semiRing) { if (a.length - offsetA < B.numRows) throw new IllegalArgumentException("Length of 'a' isn't long enough"); if (c.length - offsetC < B.numCols) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java index 5f582ebff..11c3d19d1 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java @@ -18,26 +18,14 @@ package org.ejml.sparse.csc.misc; -import org.apache.commons.math3.util.Pair; -import org.ejml.EjmlUnitTests; -import org.ejml.UtilEjml; -import org.ejml.data.DMatrixSparse; import org.ejml.data.DMatrixSparseCSC; -import org.ejml.data.IGrowArray; -import org.ejml.ops.DoubleSemiRing; -import org.ejml.ops.PreDefinedDoubleSemiRings; -import org.ejml.sparse.csc.CommonOps_DSCC; -import org.ejml.sparse.csc.MatrixFeatures_DSCC; -import org.ejml.sparse.csc.RandomMatrices_DSCC; -import org.junit.jupiter.api.Test; +import org.ejml.ops.DSemiRing; +import org.ejml.ops.DSemiRings; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -import java.util.HashMap; -import java.util.Random; -import java.util.Set; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -47,7 +35,7 @@ public class TestImplCommonOpsWithSemiRing_DSCC { @ParameterizedTest @MethodSource("elementWiseAddSource") - public void add(DoubleSemiRing semiRing, double[] expected) { + public void add(DSemiRing semiRing, double[] expected) { // == graph unions DMatrixSparseCSC a = new DMatrixSparseCSC(3, 3); DMatrixSparseCSC b = a.copy(); @@ -68,7 +56,7 @@ public void add(DoubleSemiRing semiRing, double[] expected) { @ParameterizedTest @MethodSource("elementWiseMultSource") - public void testelementWiseMult(DoubleSemiRing semiRing, double[] expected) { + public void testelementWiseMult(DSemiRing semiRing, double[] expected) { // == graph intersection DMatrixSparseCSC matrix = new DMatrixSparseCSC(3, 3, 4); matrix.set(1, 1, 4); @@ -93,18 +81,18 @@ public void testelementWiseMult(DoubleSemiRing semiRing, double[] expected) { private static Stream elementWiseAddSource() { return Stream.of( - Arguments.of(PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{4, 5}), - Arguments.of(PreDefinedDoubleSemiRings.MIN_MAX, new double[]{4, 2}), - Arguments.of(PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1}) + Arguments.of(DSemiRings.PLUS_TIMES, new double[]{4, 5}), + Arguments.of(DSemiRings.MIN_MAX, new double[]{4, 2}), + Arguments.of(DSemiRings.OR_AND, new double[]{1, 1}) ); } private static Stream elementWiseMultSource() { return Stream.of( - Arguments.of(PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{12, -2}), - Arguments.of(PreDefinedDoubleSemiRings.PLUS_MIN, new double[]{3, -2}), - Arguments.of(PreDefinedDoubleSemiRings.MIN_MAX, new double[]{4, 1}), - Arguments.of(PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1}) + Arguments.of(DSemiRings.PLUS_TIMES, new double[]{12, -2}), + Arguments.of(DSemiRings.PLUS_MIN, new double[]{3, -2}), + Arguments.of(DSemiRings.MIN_MAX, new double[]{4, 1}), + Arguments.of(DSemiRings.OR_AND, new double[]{1, 1}) ); } } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java index f5c0c2490..e4bd1ec95 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixVectorMultWithSemiRing_DSCC.java @@ -19,8 +19,8 @@ package org.ejml.sparse.csc.mult; import org.ejml.data.DMatrixSparseCSC; -import org.ejml.ops.DoubleSemiRing; -import org.ejml.ops.PreDefinedDoubleSemiRings; +import org.ejml.ops.DSemiRing; +import org.ejml.ops.DSemiRings; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -55,7 +55,7 @@ public void setUp() { @ParameterizedTest(name = "{0}") @MethodSource("vectorMatrixMultSources") - public void mult_v_A(String desc, DoubleSemiRing semiRing, double[] expected) { + public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { // graphblas == following outgoing edges of source nodes double v[] = new double[7]; Arrays.fill(v, semiRing.add.id); @@ -77,7 +77,7 @@ public void mult_v_A(String desc, DoubleSemiRing semiRing, double[] expected) { @ParameterizedTest(name = "{0}") @MethodSource("matrixVectorMultSources") - public void mult_A_v(String desc, DoubleSemiRing semiRing, double[] expected) { + public void mult_A_v(String desc, DSemiRing semiRing, double[] expected) { // graphblas == following incoming edges of source nodes double v[] = new double[7]; Arrays.fill(v, semiRing.add.id); @@ -99,25 +99,25 @@ public void mult_A_v(String desc, DoubleSemiRing semiRing, double[] expected) { private static Stream vectorMatrixMultSources() { return Stream.of( - Arguments.of("Plus, Times", PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{0.1, 0, 0.5, 0, 0, 0, 0}), - Arguments.of("OR, AND", PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 0, 1, 0, 0, 0, 0}), - Arguments.of("MIN, PLUS", PreDefinedDoubleSemiRings.MIN_PLUS, + Arguments.of("Plus, Times", DSemiRings.PLUS_TIMES, new double[]{0.1, 0, 0.5, 0, 0, 0, 0}), + Arguments.of("OR, AND", DSemiRings.OR_AND, new double[]{1, 0, 1, 0, 0, 0, 0}), + Arguments.of("MIN, PLUS", DSemiRings.MIN_PLUS, new double[]{0.7, Double.MAX_VALUE, 0.9, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE}), // This only works on sparse input vectors (here resulting in 1.0 instead of Double.MIN_Value as max(1.0, Double.MIN_VALUE) = 1.0) // Arguments.of("MAX, PLUS", PreDefinedDoubleSemiRings.MAX_PLUS, // new double[]{0.7, Double.MIN_VALUE, 1.1, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE}), - Arguments.of("MIN, TIMES", PreDefinedDoubleSemiRings.MIN_TIMES, + Arguments.of("MIN, TIMES", DSemiRings.MIN_TIMES, new double[]{0.1, Double.MAX_VALUE, 0.2, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE}), - Arguments.of("MAX, MIN", PreDefinedDoubleSemiRings.MAX_MIN, + Arguments.of("MAX, MIN", DSemiRings.MAX_MIN, new double[]{0.2, Double.MIN_VALUE, 0.5, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE}) ); } private static Stream matrixVectorMultSources() { return Stream.of( - Arguments.of("Plus, Times", PreDefinedDoubleSemiRings.PLUS_TIMES, new double[]{0.5, 0.6, 0, 0, 0, 0, 1.1}), - Arguments.of("OR, AND", PreDefinedDoubleSemiRings.OR_AND, new double[]{1, 1, 0, 0, 0, 0, 1}), - Arguments.of("MIN, PLUS", PreDefinedDoubleSemiRings.MIN_PLUS, + Arguments.of("Plus, Times", DSemiRings.PLUS_TIMES, new double[]{0.5, 0.6, 0, 0, 0, 0, 1.1}), + Arguments.of("OR, AND", DSemiRings.OR_AND, new double[]{1, 1, 0, 0, 0, 0, 1}), + Arguments.of("MIN, PLUS", DSemiRings.MIN_PLUS, new double[]{1.5, 1.6, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, 1.5}) ); } From 8a36c74ddf125c00039a724269d035d5d7d65612 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 28 Jul 2020 15:17:16 +0200 Subject: [PATCH 13/48] Applying previous commit to semi-ring version * Updating functions to return output if null and use common input sanity check functions --- .../csc/CommonOpsWithSemiRing_DSCC.java | 126 ++++++++++-------- 1 file changed, 70 insertions(+), 56 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 2ebf6b6b0..e7b0650ce 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -19,10 +19,7 @@ package org.ejml.sparse.csc; import org.ejml.MatrixDimensionException; -import org.ejml.data.DGrowArray; -import org.ejml.data.DMatrixRMaj; -import org.ejml.data.DMatrixSparseCSC; -import org.ejml.data.IGrowArray; +import org.ejml.data.*; import org.ejml.ops.DMonoid; import org.ejml.ops.DSemiRing; import org.ejml.ops.DUnaryOperator; @@ -32,155 +29,172 @@ import javax.annotation.Nullable; import java.util.Arrays; +import static org.ejml.UtilEjml.reshapeOrDeclare; import static org.ejml.UtilEjml.stringShapes; public class CommonOpsWithSemiRing_DSCC { - public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing) { - mult(A, B, C, semiRing, null, null); + public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing) { + return mult(A, B, output, semiRing, null, null); } /** - * Performs matrix multiplication. C = A*B + * Performs matrix multiplication. output = A*B * * @param A (Input) Matrix. Not modified. * @param B (Input) Matrix. Not modified. - * @param C (Output) Storage for results. Data length is increased if increased if insufficient. + * @param output (Output) Storage for results. Data length is increased if increased if insufficient. * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numRows, B.numCols); + output = reshapeOrDeclare(output,A,A.numRows,B.numCols); - ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, C, semiRing, gw, gx); + ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, gw, gx); + + return output; } - public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numCols, B.numCols); + output = reshapeOrDeclare(output,A,A.numCols,B.numCols); - ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, C, semiRing, gw, gx); + ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, gw, gx); + + return output; } /** - * Performs matrix multiplication. C = A*BT. B needs to be sorted and will be sorted if it + * Performs matrix multiplication. output = A*BT. B needs to be sorted and will be sorted if it * has not already been sorted. * * @param A (Input) Matrix. Not modified. * @param B (Input) Matrix. Value not modified but indicies will be sorted if not sorted already. - * @param C (Output) Storage for results. Data length is increased if increased if insufficient. + * @param output (Output) Storage for results. Data length is increased if increased if insufficient. * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numRows, B.numRows); + output = reshapeOrDeclare(output, A, A.numRows, B.numRows); if (!B.isIndicesSorted()) B.sortIndices(null); - ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, C, semiRing, gw, gx); + ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, gw, gx); + + return output; } /** - * Performs matrix multiplication. C = A*B + * Performs matrix multiplication. output = A*B * * @param A Matrix * @param B Dense Matrix - * @param C Dense Matrix + * @param output Dense Matrix */ - public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + public static DMatrixRMaj mult(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { if (A.numCols != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numRows, B.numCols); - ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, C, semiRing); + output = reshapeOrDeclare(output,A.numRows,B.numCols); + + ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing); + + return output; } /** - *

C = C + A*B

+ *

output = output + A*B

*/ - public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { - if (A.numRows != C.numRows || B.numCols != C.numCols) - throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { + if (A.numRows != output.numRows || B.numCols != output.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, output)); - ImplSparseSparseMultWithSemiRing_DSCC.multAdd(A, B, C, semiRing); + ImplSparseSparseMultWithSemiRing_DSCC.multAdd(A, B, output, semiRing); } /** - * Performs matrix multiplication. C = AT*B + * Performs matrix multiplication. output = AT*B * * @param A Matrix * @param B Dense Matrix - * @param C Dense Matrix + * @param output Dense Matrix */ - public static void multTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + public static DMatrixRMaj multTransA(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { if (A.numRows != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numCols, B.numCols); - ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, C, semiRing); + output = reshapeOrDeclare(output, A.numCols, B.numCols); + + ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing); + + return output; } /** - *

C = C + AT*B

+ *

output = output + AT*B

*/ - public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { - if (A.numCols != C.numRows || B.numCols != C.numCols) - throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj output, DSemiRing semiRing) { + if (A.numCols != output.numRows || B.numCols != output.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, output)); - ImplSparseSparseMultWithSemiRing_DSCC.multAddTransA(A, B, C, semiRing); + ImplSparseSparseMultWithSemiRing_DSCC.multAddTransA(A, B, output, semiRing); } /** - * Performs matrix multiplication. C = A*BT + * Performs matrix multiplication. output = A*BT * * @param A Matrix * @param B Dense Matrix - * @param C Dense Matrix + * @param output Dense Matrix */ - public static void multTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { - // todo: combine with multAdd as only difference is that C is filled with zero before + public static DMatrixRMaj multTransB(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { + // todo: combine with multAdd as only difference is that output is filled with zero before if (A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numRows, B.numRows); + output = reshapeOrDeclare(output, A.numRows, B.numRows); - ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, C, semiRing); + ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing); + + return output; } /** - *

C = C + A*BT

+ *

output = output + A*BT

*/ - public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { - if (A.numRows != C.numRows || B.numRows != C.numCols) - throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, C)); + public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj output, DSemiRing semiRing) { + if (A.numRows != output.numRows || B.numRows != output.numCols) + throw new IllegalArgumentException("Inconsistent matrix shapes. " + stringShapes(A, B, output)); // TODO: ? this is basically the equivalent of graphblas mult with specified accumulator op - ImplSparseSparseMultWithSemiRing_DSCC.multAddTransB(A, B, C, semiRing); + ImplSparseSparseMultWithSemiRing_DSCC.multAddTransB(A, B, output, semiRing); } /** - * Performs matrix multiplication. C = AT*BT + * Performs matrix multiplication. output = AT*BT * * @param A Matrix * @param B Dense Matrix - * @param C Dense Matrix + * @param output Dense Matrix */ - public static void multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { + public static DMatrixRMaj multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj output, DSemiRing semiRing) { if (A.numRows != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numCols, B.numRows); + output = reshapeOrDeclare(output, A.numCols, B.numRows); - ImplSparseSparseMultWithSemiRing_DSCC.multTransAB(A, B, C, semiRing); + ImplSparseSparseMultWithSemiRing_DSCC.multTransAB(A, B, output, semiRing); + + return output; } From ceea2be40253bb853fdc1b3dee0558da01d71618 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 28 Jul 2020 15:19:32 +0200 Subject: [PATCH 14/48] Remove redundant semi-ring unspecific methods --- .../csc/CommonOpsWithSemiRing_DSCC.java | 193 +----------------- 1 file changed, 4 insertions(+), 189 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index e7b0650ce..279b78404 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -19,15 +19,15 @@ package org.ejml.sparse.csc; import org.ejml.MatrixDimensionException; -import org.ejml.data.*; -import org.ejml.ops.DMonoid; +import org.ejml.data.DGrowArray; +import org.ejml.data.DMatrixRMaj; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; import org.ejml.ops.DSemiRing; -import org.ejml.ops.DUnaryOperator; import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; import org.ejml.sparse.csc.mult.ImplSparseSparseMultWithSemiRing_DSCC; import javax.annotation.Nullable; -import java.util.Arrays; import static org.ejml.UtilEjml.reshapeOrDeclare; import static org.ejml.UtilEjml.stringShapes; @@ -249,190 +249,5 @@ public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSp ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, C, semiRing, gw, gx); } - - /** - * Applies the row permutation specified by the vector to the input matrix and save the results - * in the output matrix. output[perm[j],:] = input[j,:] - * - * @param permInv (Input) Inverse permutation vector. Specifies new order of the rows. - * @param input (Input) Matrix which is to be permuted - * @param output (Output) Matrix which has the permutation stored in it. Is reshaped. - */ - public static void permuteRowInv(int permInv[], DMatrixSparseCSC input, DMatrixSparseCSC output) { - CommonOps_DSCC.permuteRowInv(permInv, input, output); - } - - /** - * Applies the forward column and inverse row permutation specified by the two vector to the input matrix - * and save the results in the output matrix. output[permRow[j],permCol[i]] = input[j,i] - * - * @param permRowInv (Input) Inverse row permutation vector. Null is the same as passing in identity. - * @param input (Input) Matrix which is to be permuted - * @param permCol (Input) Column permutation vector. Null is the same as passing in identity. - * @param output (Output) Matrix which has the permutation stored in it. Is reshaped. - */ - public static void permute(@Nullable int permRowInv[], DMatrixSparseCSC input, @Nullable int permCol[], DMatrixSparseCSC output) { - CommonOps_DSCC.permute(permRowInv, input, permCol, output); - } - - /** - * Extracts a column from A and stores it into out. - * - * @param A (Input) Source matrix. not modified. - * @param column The column in A - * @param out (Output, Optional) Storage for column vector - * @return The column of A. - */ - public static DMatrixSparseCSC extractColumn(DMatrixSparseCSC A, int column, @Nullable DMatrixSparseCSC out) { - return extractColumn(A, column, out); - } - - /** - * Creates a submatrix by extracting the specified rows from A. rows = {row0 %le; i %le; row1}. - * - * @param A (Input) matrix - * @param row0 First row. Inclusive - * @param row1 Last row+1. - * @param out (Output, Option) Storage for output matrix - * @return The submatrix - */ - public static DMatrixSparseCSC extractRows(DMatrixSparseCSC A, int row0, int row1, DMatrixSparseCSC out) { - return CommonOps_DSCC.extractRows(A, row0, row1, out); - } - - /** - *

- * Extracts a submatrix from 'src' and inserts it in a submatrix in 'dst'. - *

- *

- * si-y0 , j-x0 = oij for all y0 ≤ i < y1 and x0 ≤ j < x1
- *
- * where 'sij' is an element in the submatrix and 'oij' is an element in the - * original matrix. - *

- * - *

WARNING: This is a very slow operation for sparse matrices. The current implementation is simple but - * involves excessive memory copying.

- * - * @param src The original matrix which is to be copied. Not modified. - * @param srcX0 Start column. - * @param srcX1 Stop column+1. - * @param srcY0 Start row. - * @param srcY1 Stop row+1. - * @param dst Where the submatrix are stored. Modified. - * @param dstY0 Start row in dst. - * @param dstX0 start column in dst. - */ - public static void extract(DMatrixSparseCSC src, int srcY0, int srcY1, int srcX0, int srcX1, - DMatrixSparseCSC dst, int dstY0, int dstX0) { - CommonOps_DSCC.extract(src, srcY0, srcY1, srcX0, srcX1, dst, dstY0, dstX0); - } - - /** - * This applies a given unary function on every value stored in the matrix - * - * @param input (Input) input matrix. Not modified - * @param func Unary function accepting a double - * @param output (Input/Output) Matrix. Modified. - */ - public static void apply(DMatrixSparseCSC input, DUnaryOperator func, @Nullable DMatrixSparseCSC output) { - if (output == null) { - output = input.createLike(); - } else if (input != output) { - output.copyStructure(input); - } - - for (int i = 0; i < input.nz_values.length; i++) { - output.nz_values[i] = func.apply(input.nz_values[i]); - } - } - - public static void apply(DMatrixSparseCSC input, DUnaryOperator func) { - apply(input, func, input); - } - - /** - * This accumulates the matrix values to a scalar value - * - * @param input (Input) input matrix. Not modified - * @param initValue initial value for accumulator - * @param monoid Monoid defining "+" for accumulator += cellValue - * @return accumulated value - */ - public static double reduceScalar(DMatrixSparseCSC input, double initValue, DMonoid monoid) { - double result = initValue; - - for (int i = 0; i < input.nz_values.length; i++) { - result = monoid.func.apply(result, input.nz_values[i]); - } - - return result; - } - - public static double reduceScalar(DMatrixSparseCSC input, DMonoid monoid) { - return reduceScalar(input, 0, monoid); - } - - /** - * This accumulates the values per column to a scalar value - * - * @param input (Input) input matrix. Not modified - * @param initValue initial value for accumulator - * @param monoid Monoid defining "+" for accumulator += cellValue - * @param output output (Output) Vector, where result can be stored in - * @return a column-vector, where v[i] == values of column i reduced to scalar based on `func` - */ - public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initValue, DMonoid monoid, @Nullable DMatrixRMaj output) { - if (output == null) { - output = new DMatrixRMaj(1, input.numCols); - } else { - output.reshape(1, input.numCols); - } - - for (int col = 0; col < input.numCols; col++) { - int start = input.col_idx[col]; - int end = input.col_idx[col + 1]; - - double acc = initValue; - for (int i = start; i < end; i++) { - acc = monoid.func.apply(acc, input.nz_values[i]); - } - - // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) - output.data[col] = acc; - } - - return output; - } - - /** - * This accumulates the values per row to a scalar value - * - * @param input (Input) input matrix. Not modified - * @param initValue initial value for accumulator - * @param monoid Monoid defining "+" for accumulator += cellValue - * @param output output (Output) Vector, where result can be stored in - * @return a row-vector, where v[i] == values of row i reduced to scalar based on `func` - */ - public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue, DMonoid monoid, @Nullable DMatrixRMaj output) { - if (output == null) { - output = new DMatrixRMaj(1, input.numRows); - } else { - output.reshape(1, input.numCols); - } - // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) - Arrays.fill(output.data, initValue); - - for (int col = 0; col < input.numCols; col++) { - int start = input.col_idx[col]; - int end = input.col_idx[col + 1]; - - for (int i = start; i < end; i++) { - output.data[input.nz_rows[i]] = monoid.func.apply(output.data[input.nz_rows[i]], input.nz_values[i]); - } - } - - return output; - } } From 4355779ddf14058f920d7b962a6939a52ed66c85 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 28 Jul 2020 15:29:02 +0200 Subject: [PATCH 15/48] Test sparse matrix-matrix mul .. only a simple test where one matrix is basically just a sparse vector --- ...TestMatrixMatrixMultWithSemiRing_DSCC.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java new file mode 100644 index 000000000..10ff10324 --- /dev/null +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.mult; + +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.ops.DSemiRing; +import org.ejml.ops.DSemiRings; +import org.ejml.sparse.csc.CommonOpsWithSemiRing_DSCC; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestMatrixMatrixMultWithSemiRing_DSCC { + DMatrixSparseCSC inputMatrix; + + @BeforeEach + public void setUp() { + // based on example in http://mit.bme.hu/~szarnyas/grb/graphblas-introduction.pdf + inputMatrix = new DMatrixSparseCSC(7, 7, 12); + inputMatrix.set(0, 1, 1); + inputMatrix.set(0, 3, 1); + inputMatrix.set(1, 4, 1); + inputMatrix.set(1, 6, 1); + inputMatrix.set(2, 5, 1); + inputMatrix.set(3, 0, 0.2); + inputMatrix.set(3, 2, 0.4); + inputMatrix.set(4, 5, 1); + inputMatrix.set(5, 2, 0.5); + inputMatrix.set(6, 2, 1); + inputMatrix.set(6, 3, 1); + inputMatrix.set(6, 4, 1); + + } + + @ParameterizedTest(name = "{0}") + @MethodSource("sparseVectorMatrixMultSources") + public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { + // graphblas == following outgoing edges of source nodes + DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); + vector.set(0, 3, 0.5); + vector.set(0, 5, 0.6); + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing); + + assertEquals(expected[0], found.get(0, 0)); + assertEquals(expected[1], found.get(0, 2)); + } + + private static Stream sparseVectorMatrixMultSources() { + return Stream.of( + // expected entries for (0, 0) and (0, 2) + Arguments.of("Plus, Times", DSemiRings.PLUS_TIMES, new double[]{0.1, 0.5}), + Arguments.of("OR, AND", DSemiRings.OR_AND, new double[]{1, 1}), + Arguments.of("MIN, PLUS", DSemiRings.MIN_PLUS, new double[]{0.7, 0.9}), + Arguments.of("MAX, PLUS", DSemiRings.MAX_PLUS, new double[]{0.7, 1.1}), + Arguments.of("MIN, TIMES", DSemiRings.MIN_TIMES, new double[]{0.1, 0.2}), + Arguments.of("MAX, MIN", DSemiRings.MAX_MIN, new double[]{0.2, 0.5}) + ); + } +} \ No newline at end of file From 53d88cbecfc5d9fae3de3b1b83ca6fb49389151f Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 16:27:27 +0200 Subject: [PATCH 16/48] Remove leftover TODO and duplicated transpose method .. based on comments by @breandan --- main/ejml-core/src/org/ejml/ops/DMonoids.java | 1 - .../csc/misc/ImplCommonOpsWithSemiRing_DSCC.java | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/main/ejml-core/src/org/ejml/ops/DMonoids.java b/main/ejml-core/src/org/ejml/ops/DMonoids.java index cbd83bc1a..16c841136 100644 --- a/main/ejml-core/src/org/ejml/ops/DMonoids.java +++ b/main/ejml-core/src/org/ejml/ops/DMonoids.java @@ -31,7 +31,6 @@ public final class DMonoids { public static final DMonoid PLUS = new DMonoid(0, Double::sum); public static final DMonoid TIMES = new DMonoid(1, (a, b) -> a * b); - // TODO: performance incr. worth not using safe Math.min/max? public final static DMonoid MIN = new DMonoid(Double.MAX_VALUE, (a, b) -> (a <= b) ? a : b); public final static DMonoid MAX = new DMonoid(Double.MIN_VALUE, (a, b) -> (a >= b) ? a : b); } \ No newline at end of file diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index 21c1fca8d..c54227eaf 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -34,17 +34,6 @@ */ public class ImplCommonOpsWithSemiRing_DSCC { - /** - * Performs a matrix transpose. - * - * @param A Original matrix. Not modified. - * @param C Storage for transposed 'a'. Reshaped. - * @param gw (Optional) Storage for internal workspace. Can be null. - */ - public static void transpose(DMatrixSparseCSC A, DMatrixSparseCSC C, @Nullable IGrowArray gw) { - ImplCommonOps_DSCC.transpose(A, C, gw); - } - /** * Performs matrix addition:
* C = A + B From 599df5c407b001dd537c79968277ccc1aae254a9 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 16:45:29 +0200 Subject: [PATCH 17/48] Make `add` and `elementMult` also return the matrix .. in order to support null as a result field, making it consistent with other ops --- .../csc/CommonOpsWithSemiRing_DSCC.java | 41 ++++++++++--------- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 35 ++++++++-------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 279b78404..8709de08d 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -211,43 +211,46 @@ public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj /** * Performs matrix addition:
- * C = αA + βB + * output = αA + βB * - * @param alpha scalar value multiplied against A - * @param A Matrix - * @param beta scalar value multiplied against B - * @param B Matrix - * @param C Output matrix. - * @param gw (Optional) Storage for internal workspace. Can be null. - * @param gx (Optional) Storage for internal workspace. Can be null. + * @param alpha scalar value multiplied against A + * @param A Matrix + * @param beta scalar value multiplied against B + * @param B Matrix + * @param output (Optional) Output matrix. + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows || A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - C.reshape(A.numRows, A.numCols); + output = reshapeOrDeclare(output, A, A.numRows, A.numCols); - ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, C, semiRing, gw, gx); + ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, gw, gx); + + return output; } /** * Performs an element-wise multiplication.
- * C[i,j] = A[i,j]*B[i,j]
+ * output[i,j] = A[i,j]*B[i,j]
* All matrices must have the same shape. - * - * @param A (Input) Matrix. + * @param A (Input) Matrix. * @param B (Input) Matrix - * @param C (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. + * @param output (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); - C.reshape(A.numRows, A.numCols); + output = reshapeOrDeclare(output, A, A.numRows, A.numCols); - ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, C, semiRing, gw, gx); + ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, gw, gx); + + return output; } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index 26cf44fc5..e4512d444 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -352,24 +352,26 @@ public static void symmLowerToFull( DMatrixSparseCSC A , DMatrixSparseCSC B , @N /** * Performs matrix addition:
- * C = αA + βB - * - * @param alpha scalar value multiplied against A + * output = αA + βB + * @param alpha scalar value multiplied against A * @param A Matrix * @param beta scalar value multiplied against B * @param B Matrix - * @param C Output matrix. + * @param output Output matrix. * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. + * @return */ - public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) + public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if( A.numRows != B.numRows || A.numCols != B.numCols ) throw new MatrixDimensionException("Inconsistent matrix shapes. "+stringShapes(A,B)); - C.reshape(A.numRows,A.numCols); + output = reshapeOrDeclare(output, A, A.numRows,A.numCols); + + ImplCommonOps_DSCC.add(alpha,A,beta,B,output, gw, gx); - ImplCommonOps_DSCC.add(alpha,A,beta,B,C, gw, gx); + return output; } public static DMatrixSparseCSC identity(int length ) { @@ -578,22 +580,23 @@ public static double elementSum( DMatrixSparseCSC A ) { /** * Performs an element-wise multiplication.
- * C[i,j] = A[i,j]*B[i,j]
+ * output[i,j] = A[i,j]*B[i,j]
* All matrices must have the same shape. - * - * @param A (Input) Matrix. + * @param A (Input) Matrix. * @param B (Input) Matrix - * @param C (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. + * @param output (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void elementMult( DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C , - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output , + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if( A.numCols != B.numCols || A.numRows != B.numRows ) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. "+stringShapes(A,B)); - C.reshape(A.numRows,A.numCols); + output = reshapeOrDeclare(output, A, A.numRows,A.numCols); + + ImplCommonOps_DSCC.elementMult(A,B,output,gw,gx); - ImplCommonOps_DSCC.elementMult(A,B,C,gw,gx); + return output; } /** From 624e0b913ccb434f02ed586d0f5c12a8e61cbc23 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 16:55:54 +0200 Subject: [PATCH 18/48] Add more test for MatrixMatrix ops with semi-ring --- ...TestMatrixMatrixMultWithSemiRing_DSCC.java | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java index 10ff10324..c5510f96b 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java @@ -18,19 +18,22 @@ package org.ejml.sparse.csc.mult; +import org.ejml.EjmlUnitTests; import org.ejml.data.DMatrixSparseCSC; import org.ejml.ops.DSemiRing; import org.ejml.ops.DSemiRings; import org.ejml.sparse.csc.CommonOpsWithSemiRing_DSCC; +import org.ejml.sparse.csc.CommonOps_DSCC; +import org.ejml.sparse.csc.RandomMatrices_DSCC; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.Random; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; public class TestMatrixMatrixMultWithSemiRing_DSCC { DMatrixSparseCSC inputMatrix; @@ -68,6 +71,50 @@ public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { assertEquals(expected[1], found.get(0, 2)); } + @ParameterizedTest(name = "{0}") + @MethodSource("sparseMatrixSources") + public void mult_AT_B(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { + DSemiRing semiRing = DSemiRings.PLUS_TIMES; + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransA(matrix, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC expected = CommonOps_DSCC.multTransA(matrix, otherMatrix, null, null, null); + + EjmlUnitTests.assertEquals(expected, found); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("sparseMatrixSources") + public void mult_A_BT(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { + DSemiRing semiRing = DSemiRings.PLUS_TIMES; + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransB(matrix, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC expected = CommonOps_DSCC.multTransB(matrix, otherMatrix, null, null, null); + + EjmlUnitTests.assertEquals(expected, found); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("sparseMatrixSources") + public void elementMult(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { + DSemiRing semiRing = DSemiRings.PLUS_TIMES; + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC expected = CommonOps_DSCC.elementMult(matrix, otherMatrix, null, null, null); + + EjmlUnitTests.assertEquals(expected, found); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("sparseMatrixSources") + public void add(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { + DSemiRing semiRing = DSemiRings.PLUS_TIMES; + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, matrix, 1, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC expected = CommonOps_DSCC.add(1, matrix, 1, otherMatrix, null, null, null); + + EjmlUnitTests.assertEquals(expected, found); + } + private static Stream sparseVectorMatrixMultSources() { return Stream.of( // expected entries for (0, 0) and (0, 2) @@ -79,4 +126,16 @@ private static Stream sparseVectorMatrixMultSources() { Arguments.of("MAX, MIN", DSemiRings.MAX_MIN, new double[]{0.2, 0.5}) ); } + + private static Stream sparseMatrixSources() { + Random rand = new Random(42); + DMatrixSparseCSC sparseMatrix = RandomMatrices_DSCC.rectangle(10, 10, 15, rand); + DMatrixSparseCSC denseMatrix = RandomMatrices_DSCC.rectangle(10, 10, 90, rand); + return Stream.of( + Arguments.of("Both really sparse", sparseMatrix, sparseMatrix), + Arguments.of("Sparse, dense", sparseMatrix, denseMatrix), + Arguments.of("dense, sparse", denseMatrix, sparseMatrix), + Arguments.of("dense, dense", denseMatrix, denseMatrix) + ); + } } \ No newline at end of file From 762a8c00dc2667b6d09c8edd251180cd9da66468 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 3 Aug 2020 13:35:12 +0200 Subject: [PATCH 19/48] Add masks based on matrices or primitive arrays --- .../src/org/ejml/GenerateJavaCode32.java | 8 +++ main/ejml-core/src/org/ejml/masks/Mask.java | 38 +++++++++++ main/ejml-core/src/org/ejml/masks/Masks.java | 60 ++++++++++++++++++ .../src/org/ejml/masks/PrimitiveDMask.java | 48 ++++++++++++++ .../src/org/ejml/masks/SparseDMask.java | 35 +++++++++++ .../org/ejml/masks/SparseStructuralMask.java | 39 ++++++++++++ .../org/ejml/masks/TestPrimitiveDMasks.java | 63 +++++++++++++++++++ .../test/org/ejml/masks/TestSparseDMasks.java | 49 +++++++++++++++ .../ejml/masks/TestSparseStructuralMasks.java | 49 +++++++++++++++ 9 files changed, 389 insertions(+) create mode 100644 main/ejml-core/src/org/ejml/masks/Mask.java create mode 100644 main/ejml-core/src/org/ejml/masks/Masks.java create mode 100644 main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java create mode 100644 main/ejml-core/src/org/ejml/masks/SparseDMask.java create mode 100644 main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java create mode 100644 main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java create mode 100644 main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java create mode 100644 main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java diff --git a/main/autocode/src/org/ejml/GenerateJavaCode32.java b/main/autocode/src/org/ejml/GenerateJavaCode32.java index 8330776f8..da2966f01 100644 --- a/main/autocode/src/org/ejml/GenerateJavaCode32.java +++ b/main/autocode/src/org/ejml/GenerateJavaCode32.java @@ -62,6 +62,10 @@ public GenerateJavaCode32() { prefix32.add("FMonoids"); prefix64.add("DSemiRings"); prefix32.add("FSemiRings"); + prefix64.add("PrimitiveDMask"); + prefix32.add("PrimitiveFMask"); + prefix64.add("SparseDMask"); + prefix32.add("SparseFMask"); prefix64.add("DScalar"); prefix32.add("FScalar"); prefix64.add("DMatrix"); @@ -103,6 +107,8 @@ public GenerateJavaCode32() { converter.replacePattern("DBinary", "FBinary"); converter.replacePattern("DMonoid", "FMonoid"); converter.replacePattern("DSemiRing", "FSemiRing"); + converter.replacePattern("SparseDMask", "SparseFMask"); + converter.replacePattern("PrimitiveDMask", "PrimitiveFMask"); converter.replacePattern("ConvertD", "ConvertF"); converter.replacePattern("DGrowArray", "FGrowArray"); converter.replacePattern("DMatrix", "FMatrix"); @@ -143,6 +149,8 @@ public static void main(String[] args ) { "main/ejml-core/test/org/ejml/data", "main/ejml-core/src/org/ejml/ops", "main/ejml-core/test/org/ejml/ops", + "main/ejml-core/src/org/ejml/masks", + "main/ejml-core/test/org/ejml/masks", "main/ejml-experimental/src/org/ejml/dense/row/decomposition/bidiagonal/" }; diff --git a/main/ejml-core/src/org/ejml/masks/Mask.java b/main/ejml-core/src/org/ejml/masks/Mask.java new file mode 100644 index 000000000..476cbef14 --- /dev/null +++ b/main/ejml-core/src/org/ejml/masks/Mask.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + + +/** + * Mask used for specifying which matrix entries should be computed + */ +public abstract class Mask { + // useful for sparse matrices, as actual negation would be costly and result in dense masks + public final boolean negated; + + public Mask(boolean negated) { + this.negated = negated; + } + + public abstract boolean isSet(int row, int col); + + // TODO: use an Iterator as it should be faster as stepping can be used -> no need to call for each entry? + // Problem .. dense matrices are row-based, whereas existing sparse matrix format is column based + //public abstract Iterator +} diff --git a/main/ejml-core/src/org/ejml/masks/Masks.java b/main/ejml-core/src/org/ejml/masks/Masks.java new file mode 100644 index 000000000..bae287b44 --- /dev/null +++ b/main/ejml-core/src/org/ejml/masks/Masks.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +import org.ejml.data.*; + +/** + * Helper class to create the corresponding mask based on a matrix or primitive array + */ +public class Masks { + public static Mask of(double[] values, boolean negated) { + return new PrimitiveDMask(values, negated); + } + + public static Mask of(float[] values, boolean negated) { + return new PrimitiveFMask(values, negated); + } + + public static Mask of(DMatrixD1 matrix, boolean negated) { + return new PrimitiveDMask(matrix.data, matrix.numCols, negated); + } + + public static Mask of(FMatrixD1 matrix, boolean negated) { + return new PrimitiveFMask(matrix.data, matrix.numCols, negated); + } + + public static Mask of(DMatrixSparseCSC matrix, boolean negated, boolean structural) { + if (structural) { + return new SparseStructuralMask(matrix, negated); + } + else { + return new SparseDMask(matrix, negated); + } + } + + public static Mask of(FMatrixSparseCSC matrix, boolean negated, boolean structural) { + if (structural) { + return new SparseStructuralMask(matrix, negated); + } + else { + return new SparseFMask(matrix, negated); + } + } +} diff --git a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java new file mode 100644 index 000000000..3ec7c33e8 --- /dev/null +++ b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +public class PrimitiveDMask extends Mask { + // TODO: Check if creating dense boolean mask is worth it (currently converting to boolean on the fly) + private final double[] values; + // alternatively declare extra separate masks for primitive and dense DMatrix + private final int numCols; + + public PrimitiveDMask(double[] values, boolean negated) { + // for dense structures they cannot be used for structural masks + this(values, 1, negated); + } + + public PrimitiveDMask(double[] values, int numCols, boolean negated) { + // for dense structures they cannot be used for structural masks + super(negated); + this.values = values; + this.numCols = numCols; + } + + @Override + public boolean isSet(int row, int col) { + // XOR as negated flips the mask flag + return negated ^ (values[row * numCols + col] != 0); + } + + public boolean isSet(int index) { + return negated ^ (values[index] != 0); + } +} diff --git a/main/ejml-core/src/org/ejml/masks/SparseDMask.java b/main/ejml-core/src/org/ejml/masks/SparseDMask.java new file mode 100644 index 000000000..ab2d6778b --- /dev/null +++ b/main/ejml-core/src/org/ejml/masks/SparseDMask.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +import org.ejml.data.DMatrixSparseCSC; + +public class SparseDMask extends Mask { + protected final DMatrixSparseCSC matrix; + + public SparseDMask(DMatrixSparseCSC matrix, boolean negated) { + super(negated); + this.matrix = matrix; + } + + @Override + public boolean isSet(int row, int col) { + return negated ^ (matrix.unsafe_get(row, col) != 0); + } +} diff --git a/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java b/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java new file mode 100644 index 000000000..84dbea0e2 --- /dev/null +++ b/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +import org.ejml.data.MatrixSparse; + +/** + * only looking if the entry is assigned in the source(disregarding the actual stored value) + * ! it does not copy the input matrix -> changing the matrix structure will also affect the mask + */ +public class SparseStructuralMask extends Mask { + private final MatrixSparse matrix; + + public SparseStructuralMask(MatrixSparse matrix, boolean negated) { + super(negated); + this.matrix = matrix; + } + + @Override + public boolean isSet(int row, int col) { + return negated ^ matrix.isAssigned(row, col); + } +} diff --git a/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java b/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java new file mode 100644 index 000000000..6ac133893 --- /dev/null +++ b/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +import org.ejml.data.DMatrixRMaj; +import org.ejml.dense.row.RandomMatrices_DDRM; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestPrimitiveDMasks { + + @Test + void testBasedOnPrimitiveArrays() { + double[] values = {2, 0, 4, 0, 0, -1}; + + PrimitiveDMask mask = new PrimitiveDMask(values, false); + PrimitiveDMask negated_mask = new PrimitiveDMask(values, true); + + boolean[] expected = {true, false, true, false, false, true}; + + for (int i = 0; i < values.length; i++) { + assertEquals(mask.isSet(i), expected[i]); + assertEquals(negated_mask.isSet(i), !expected[i]); + } + } + + @Test + void testBasedOnDenseMatrix() { + int dim = 20; + DMatrixRMaj matrix = RandomMatrices_DDRM.rectangle(dim, dim, new Random(42)); + + PrimitiveDMask mask = new PrimitiveDMask(matrix.data, matrix.numCols, false); + PrimitiveDMask negated_mask = new PrimitiveDMask(matrix.data, matrix.numCols, true); + + + for (int row = 0; row < dim; row++) { + for (int col = 0; col < dim; col++) { + boolean expected = (matrix.get(row, col) != 0); + assertEquals(mask.isSet(row, col), expected); + assertEquals(negated_mask.isSet(row, col), !expected); + } + } + } +} diff --git a/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java b/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java new file mode 100644 index 000000000..12197b602 --- /dev/null +++ b/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.sparse.csc.RandomMatrices_DSCC; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestSparseDMasks { + + @Test + void testSparseMasks() { + int dim = 20; + DMatrixSparseCSC matrix = RandomMatrices_DSCC.rectangle(dim, dim, 5, new Random(42)); + + Mask mask = new SparseDMask(matrix, false); + Mask negated_mask = new SparseDMask(matrix, true); + + var it = matrix.createCoordinateIterator(); + + for (int row = 0; row < dim; row++) { + for (int col = 0; col < dim; col++) { + boolean expected = (matrix.get(row, col) != 0); + assertEquals(mask.isSet(row, col), expected); + assertEquals(negated_mask.isSet(row, col), !expected); + } + } + } +} diff --git a/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java b/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java new file mode 100644 index 000000000..8a6e3b1c0 --- /dev/null +++ b/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.sparse.csc.RandomMatrices_DSCC; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestSparseStructuralMasks { + + @Test + void testSparseStructuralMasks() { + int dim = 20; + DMatrixSparseCSC matrix = RandomMatrices_DSCC.rectangle(dim, dim, 5, new Random(42)); + + Mask mask = new SparseStructuralMask(matrix, false); + Mask negated_mask = new SparseStructuralMask(matrix, true); + + var it = matrix.createCoordinateIterator(); + + for (int row = 0; row < dim; row++) { + for (int col = 0; col < dim; col++) { + boolean expected = matrix.isAssigned(row, col); + assertEquals(mask.isSet(row, col), expected); + assertEquals(negated_mask.isSet(row, col), !expected); + } + } + } +} From 4461a0c880850e47c5b97e5d2d992923c2ec2ad8 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 3 Aug 2020 16:35:37 +0200 Subject: [PATCH 20/48] Add a zeroElement to Masks .. except StructualMasks as they dont need one + autogenerate Mask helper class for floats (reducing similar code) --- .../src/org/ejml/GenerateJavaCode32.java | 3 ++ .../ejml/masks/{Masks.java => DMasks.java} | 30 +++++++++---------- .../src/org/ejml/masks/PrimitiveDMask.java | 18 ++++++++--- .../src/org/ejml/masks/SparseDMask.java | 8 ++++- 4 files changed, 38 insertions(+), 21 deletions(-) rename main/ejml-core/src/org/ejml/masks/{Masks.java => DMasks.java} (66%) diff --git a/main/autocode/src/org/ejml/GenerateJavaCode32.java b/main/autocode/src/org/ejml/GenerateJavaCode32.java index da2966f01..9b50f306f 100644 --- a/main/autocode/src/org/ejml/GenerateJavaCode32.java +++ b/main/autocode/src/org/ejml/GenerateJavaCode32.java @@ -66,6 +66,8 @@ public GenerateJavaCode32() { prefix32.add("PrimitiveFMask"); prefix64.add("SparseDMask"); prefix32.add("SparseFMask"); + prefix64.add("DMasks"); + prefix32.add("FMasks"); prefix64.add("DScalar"); prefix32.add("FScalar"); prefix64.add("DMatrix"); @@ -109,6 +111,7 @@ public GenerateJavaCode32() { converter.replacePattern("DSemiRing", "FSemiRing"); converter.replacePattern("SparseDMask", "SparseFMask"); converter.replacePattern("PrimitiveDMask", "PrimitiveFMask"); + converter.replacePattern("DMasks", "FMasks"); converter.replacePattern("ConvertD", "ConvertF"); converter.replacePattern("DGrowArray", "FGrowArray"); converter.replacePattern("DMatrix", "FMatrix"); diff --git a/main/ejml-core/src/org/ejml/masks/Masks.java b/main/ejml-core/src/org/ejml/masks/DMasks.java similarity index 66% rename from main/ejml-core/src/org/ejml/masks/Masks.java rename to main/ejml-core/src/org/ejml/masks/DMasks.java index bae287b44..1a4add7b8 100644 --- a/main/ejml-core/src/org/ejml/masks/Masks.java +++ b/main/ejml-core/src/org/ejml/masks/DMasks.java @@ -23,38 +23,36 @@ /** * Helper class to create the corresponding mask based on a matrix or primitive array */ -public class Masks { +public class DMasks { + // TODO: use builder? + public static Mask of(double[] values, boolean negated) { - return new PrimitiveDMask(values, negated); + return of(values, negated, 0); } - public static Mask of(float[] values, boolean negated) { - return new PrimitiveFMask(values, negated); + public static Mask of(double[] values, boolean negated, double zeroElement) { + return new PrimitiveDMask(values, negated, zeroElement); } public static Mask of(DMatrixD1 matrix, boolean negated) { - return new PrimitiveDMask(matrix.data, matrix.numCols, negated); + return of(matrix, negated, 0); } - public static Mask of(FMatrixD1 matrix, boolean negated) { - return new PrimitiveFMask(matrix.data, matrix.numCols, negated); + public static Mask of(DMatrixD1 matrix, boolean negated, double zeroElement) { + return new PrimitiveDMask(matrix.data, matrix.numCols, negated, zeroElement); } - public static Mask of(DMatrixSparseCSC matrix, boolean negated, boolean structural) { + public static Mask of(DMatrixSparseCSC matrix, boolean negated, boolean structural, double zeroElement){ if (structural) { return new SparseStructuralMask(matrix, negated); } else { - return new SparseDMask(matrix, negated); + return new SparseDMask(matrix, negated, zeroElement); } } - public static Mask of(FMatrixSparseCSC matrix, boolean negated, boolean structural) { - if (structural) { - return new SparseStructuralMask(matrix, negated); - } - else { - return new SparseFMask(matrix, negated); - } + // structural masks cannot have a zeroElement + public static Mask of(DMatrixSparseCSC matrix, boolean negated, boolean structural) { + return of(matrix, negated, structural, 0); } } diff --git a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java index 3ec7c33e8..c946fd1cd 100644 --- a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java +++ b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java @@ -23,26 +23,36 @@ public class PrimitiveDMask extends Mask { private final double[] values; // alternatively declare extra separate masks for primitive and dense DMatrix private final int numCols; + // + private final double zeroElement; public PrimitiveDMask(double[] values, boolean negated) { - // for dense structures they cannot be used for structural masks - this(values, 1, negated); + this(values, 1, negated, 0); + } + + public PrimitiveDMask(double[] values, boolean negated, double zeroElement) { + this(values, 1, negated, zeroElement); } public PrimitiveDMask(double[] values, int numCols, boolean negated) { + this(values, numCols, negated, 0); + } + + public PrimitiveDMask(double[] values, int numCols, boolean negated, double zeroElement) { // for dense structures they cannot be used for structural masks super(negated); this.values = values; this.numCols = numCols; + this.zeroElement = zeroElement; } @Override public boolean isSet(int row, int col) { // XOR as negated flips the mask flag - return negated ^ (values[row * numCols + col] != 0); + return negated ^ (values[row * numCols + col] != zeroElement); } public boolean isSet(int index) { - return negated ^ (values[index] != 0); + return negated ^ (values[index] != zeroElement); } } diff --git a/main/ejml-core/src/org/ejml/masks/SparseDMask.java b/main/ejml-core/src/org/ejml/masks/SparseDMask.java index ab2d6778b..702ec0174 100644 --- a/main/ejml-core/src/org/ejml/masks/SparseDMask.java +++ b/main/ejml-core/src/org/ejml/masks/SparseDMask.java @@ -22,14 +22,20 @@ public class SparseDMask extends Mask { protected final DMatrixSparseCSC matrix; + protected final double zeroElement; public SparseDMask(DMatrixSparseCSC matrix, boolean negated) { + this(matrix, negated, 0); + } + + public SparseDMask(DMatrixSparseCSC matrix, boolean negated, double zeroElement) { super(negated); this.matrix = matrix; + this.zeroElement = zeroElement; } @Override public boolean isSet(int row, int col) { - return negated ^ (matrix.unsafe_get(row, col) != 0); + return negated ^ (matrix.unsafe_get(row, col) != zeroElement); } } From 7ad758cddfeccd27405f269948ce751670db5614 Mon Sep 17 00:00:00 2001 From: Florentin Doerre Date: Tue, 4 Aug 2020 17:06:37 +0200 Subject: [PATCH 21/48] Init masked operations --- .../csc/CommonOpsWithSemiRing_DSCC.java | 44 ++++++----- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 5 ++ .../misc/ImplCommonOpsWithSemiRing_DSCC.java | 18 +++-- ...ImplSparseSparseMultWithSemiRing_DSCC.java | 53 ++++++++----- .../MatrixVectorMultWithSemiRing_DSCC.java | 78 +++++++++---------- .../TestImplCommonOpsWithSemiRing_DSCC.java | 4 +- ...TestMatrixMatrixMultWithSemiRing_DSCC.java | 2 +- 7 files changed, 115 insertions(+), 89 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 8709de08d..f4e563d18 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -23,6 +23,7 @@ import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; +import org.ejml.masks.Mask; import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; import org.ejml.sparse.csc.mult.ImplSparseSparseMultWithSemiRing_DSCC; @@ -35,8 +36,9 @@ public class CommonOpsWithSemiRing_DSCC { - public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing) { - return mult(A, B, output, semiRing, null, null); + public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, + @Nullable Mask mask) { + return mult(A, B, output, semiRing, mask, null, null); } /** @@ -45,27 +47,28 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul * @param A (Input) Matrix. Not modified. * @param B (Input) Matrix. Not modified. * @param output (Output) Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); output = reshapeOrDeclare(output,A,A.numRows,B.numCols); - ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, gw, gx); + ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, mask, gw, gx); return output; } public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); output = reshapeOrDeclare(output,A,A.numCols,B.numCols); - ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, gw, gx); + ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, mask, gw, gx); return output; } @@ -77,24 +80,23 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B * @param A (Input) Matrix. Not modified. * @param B (Input) Matrix. Value not modified but indicies will be sorted if not sorted already. * @param output (Output) Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); output = reshapeOrDeclare(output, A, A.numRows, B.numRows); - - if (!B.isIndicesSorted()) - B.sortIndices(null); - - ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, gw, gx); - + if (!B.isIndicesSorted()) B.sortIndices(null); + ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, mask, gw, gx); return output; } + // sparse-dense variations + /** * Performs matrix multiplication. output = A*B * @@ -218,16 +220,17 @@ public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj * @param beta scalar value multiplied against B * @param B Matrix * @param output (Optional) Output matrix. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows || A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); output = reshapeOrDeclare(output, A, A.numRows, A.numCols); - ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, gw, gx); + ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, mask, gw, gx); return output; } @@ -238,19 +241,18 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta * All matrices must have the same shape. * @param A (Input) Matrix. * @param B (Input) Matrix - * @param output (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. + * @param output (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); output = reshapeOrDeclare(output, A, A.numRows, A.numCols); - ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, gw, gx); - - return output; + ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, C, semiRing, mask, gw, gx); } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index e4512d444..3392902bf 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -1880,6 +1880,10 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func } else if (input != output) { output.copyStructure(input); } + // TODO: how to apply mask (space/time tradeoff) + // only compute value if mask.isSet -> no SIMDI usage possible + // compute in tmp matrix -> memory usage doubled + // also here Mask needs to be based on nz_value index for (int i = 0; i < input.nz_length; i++) { output.nz_values[i] = func.apply(input.nz_values[i]); @@ -1981,6 +1985,7 @@ public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue output.reshape(1, input.numCols); } // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) + // TODO: use tmp array and than set to output (there regard mask) Arrays.fill(output.data, initValue); for (int col = 0; col < input.numCols; col++) { diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index c54227eaf..0a5a4b06c 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -21,6 +21,7 @@ import org.ejml.data.DGrowArray; import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; +import org.ejml.masks.Mask; import org.ejml.ops.DSemiRing; import javax.annotation.Nullable; @@ -41,10 +42,11 @@ public class ImplCommonOpsWithSemiRing_DSCC { * @param A Matrix * @param B Matrix * @param C Output matrix. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -63,7 +65,10 @@ public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSpa int idxC1 = C.col_idx[col + 1]; for (int i = idxC0; i < idxC1; i++) { - C.nz_values[i] = x[C.nz_rows[i]]; + // very likely not vectorized now ... + if(mask == null || mask.isSet(C.nz_rows[i], col)) { + C.nz_values[i] = x[C.nz_rows[i]]; + } } } C.col_idx[A.numCols] = C.nz_length; @@ -122,10 +127,11 @@ public static void addColAppend(DMatrixSparseCSC A, int colA, DMatrixSparseCSC B * @param A (Input) Matrix * @param B (Input) Matrix * @param C (Output) Matrix. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows); @@ -162,8 +168,10 @@ public static void elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSp for (int i = idxB0; i < idxB1; i++) { int row = B.nz_rows[i]; if (w[row] == col) { - C.nz_values[C.nz_length] = semiRing.mult.func.apply(x[row], B.nz_values[i]); - C.nz_rows[C.nz_length++] = row; + if (mask == null || mask.isSet(row, col)) { + C.nz_values[C.nz_length] = semiRing.mult.func.apply(x[row], B.nz_values[i]); + C.nz_rows[C.nz_length++] = row; + } } } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 4a46c727b..5e2a97899 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -22,6 +22,7 @@ import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; +import org.ejml.masks.Mask; import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.CommonOps_DSCC; @@ -40,10 +41,11 @@ public class ImplSparseSparseMultWithSemiRing_DSCC { * @param A Matrix * @param B Matrix * @param C Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -76,7 +78,10 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC int idxC1 = C.col_idx[colB + 1]; for (int i = idxC0; i < idxC1; i++) { - C.nz_values[i] = x[C.nz_rows[i]]; + // this will destroy simdi usage here .. (hopefully not if mask == null) + if (mask == null || mask.isSet(C.nz_rows[i], bj)) { + C.nz_values[i] = x[C.nz_rows[i]]; + } } idx0 = idx1; @@ -90,10 +95,11 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC * @param A Matrix * @param B Matrix * @param C Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -116,28 +122,31 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSpa for (int bi = idxB0; bi < idxB1; bi++) { int rowB = B.nz_rows[bi]; x[rowB] = B.nz_values[bi]; + // TODO: wouldn't a BitSet also work instead of an IGrowArray? w[rowB] = bj; } // C(colA,colB) = A(:,colA)*B(:,colB) for (int colA = 0; colA < A.numCols; colA++) { - int idxA0 = A.col_idx[colA]; - int idxA1 = A.col_idx[colA + 1]; - - double sum = semiRing.add.id; - for (int ai = idxA0; ai < idxA1; ai++) { - int rowA = A.nz_rows[ai]; - if (w[rowA] == bj) { - sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(x[rowA], A.nz_values[ai])); + if (mask == null || mask.isSet(colA, bj)) { + int idxA0 = A.col_idx[colA]; + int idxA1 = A.col_idx[colA + 1]; + + double sum = semiRing.add.id; + for (int ai = idxA0; ai < idxA1; ai++) { + int rowA = A.nz_rows[ai]; + if (w[rowA] == bj) { + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(x[rowA], A.nz_values[ai])); + } } - } - if (sum != semiRing.add.id) { - if (C.nz_length == C.nz_values.length) { - C.growMaxLength(C.nz_length * 2 + 1, true); + if (sum != semiRing.add.id) { + if (C.nz_length == C.nz_values.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } + C.nz_values[C.nz_length] = sum; + C.nz_rows[C.nz_length++] = colA; } - C.nz_values[C.nz_length] = sum; - C.nz_rows[C.nz_length++] = colA; } } C.col_idx[bj] = C.nz_length; @@ -151,10 +160,11 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSpa * @param A Matrix * @param B Matrix * @param C Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static void multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (!B.isIndicesSorted()) throw new IllegalArgumentException("B must have its indices sorted."); @@ -195,7 +205,10 @@ else if (!CommonOps_DSCC.checkIndicesSorted(B)) { int idxC1 = C.col_idx[colC + 1]; for (int i = idxC0; i < idxC1; i++) { - C.nz_values[i] = x[C.nz_rows[i]]; + // this will destroy simdi usage here .. (hopefully not if mask == null) + if(mask == null || mask.isSet(C.nz_rows[i], colC)) { + C.nz_values[i] = x[C.nz_rows[i]]; + } } } } @@ -231,6 +244,8 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, } } + // sparse-dense variants + public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { C.fill(semiRing.add.id); multAdd(A, B, C, semiRing); diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java index eadd75bf5..5c8ea2ae9 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java @@ -19,8 +19,10 @@ package org.ejml.sparse.csc.mult; import org.ejml.data.DMatrixSparseCSC; +import org.ejml.masks.PrimitiveDMask; import org.ejml.ops.DSemiRing; +import javax.annotation.Nullable; import java.util.Arrays; /** @@ -30,21 +32,26 @@ public class MatrixVectorMultWithSemiRing_DSCC { /** * c = A*b * - * @param A (Input) Matrix - * @param b (Input) vector - * @param offsetB (Input) first index in vector b - * @param c (Output) vector - * @param offsetC (Output) first index in vector c + * @param A (Input) Matrix + * @param b (Input) vector + * @param c (Output) vector + * @param mask Mask for specifying which entries should be overwritten */ - public static void mult(DMatrixSparseCSC A, - double b[], int offsetB, - double c[], int offsetC, DSemiRing semiRing) { - Arrays.fill(c, offsetC, offsetC + A.numRows, semiRing.add.id); - multAdd(A, b, offsetB, c, offsetC, semiRing); + public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing semiRing, @Nullable PrimitiveDMask mask) { + if (mask == null) { + Arrays.fill(c, semiRing.add.id); + } else { + for (int i = 0; i < c.length; i++) { + if (mask.isSet(i)) { + c[i] = semiRing.add.id; + } + } + } + multAdd(A, b, c, semiRing, mask); } public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing semiRing) { - mult(A, b, 0, c, 0, semiRing); + mult(A, b, c, semiRing, null); } /** @@ -52,27 +59,21 @@ public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing se * * @param A (Input) Matrix * @param b (Input) vector - * @param offsetB (Input) first index in vector b * @param c (Output) vector - * @param offsetC (Output) first index in vector c + * @param mask Mask for specifying which entries should be overwritten * @param semiRing */ - public static void multAdd(DMatrixSparseCSC A, - double[] b, int offsetB, - double[] c, int offsetC, DSemiRing semiRing) { - if (b.length - offsetB < A.numCols) - throw new IllegalArgumentException("Length of 'b' isn't long enough"); - if (c.length - offsetC < A.numRows) - throw new IllegalArgumentException("Length of 'c' isn't long enough"); - + public static void multAdd(DMatrixSparseCSC A, double[] b, double[] c, DSemiRing semiRing, @Nullable PrimitiveDMask mask) { for (int k = 0; k < A.numCols; k++) { int idx0 = A.col_idx[k]; int idx1 = A.col_idx[k + 1]; for (int indexA = idx0; indexA < idx1; indexA++) { - c[offsetC + A.nz_rows[indexA]] = semiRing.add.func.apply( - c[offsetC + A.nz_rows[indexA]], - semiRing.mult.func.apply(A.nz_values[indexA], b[offsetB + k])); + if (mask == null || mask.isSet(A.nz_rows[indexA])) { + c[A.nz_rows[indexA]] = semiRing.add.func.apply( + c[A.nz_rows[indexA]], + semiRing.mult.func.apply(A.nz_values[indexA], b[k])); + } } } } @@ -81,33 +82,28 @@ public static void multAdd(DMatrixSparseCSC A, * c = aT*B * * @param a (Input) vector - * @param offsetA Input) first index in vector a * @param B (Input) Matrix * @param c (Output) vector - * @param offsetC (Output) first index in vector c + * @param mask Mask for specifying which entries should be overwritten */ - public static void mult(double a[], int offsetA, - DMatrixSparseCSC B, - double c[], int offsetC, DSemiRing semiRing) { - if (a.length - offsetA < B.numRows) - throw new IllegalArgumentException("Length of 'a' isn't long enough"); - if (c.length - offsetC < B.numCols) - throw new IllegalArgumentException("Length of 'c' isn't long enough"); - + public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing, @Nullable PrimitiveDMask mask) { for (int k = 0; k < B.numCols; k++) { - int idx0 = B.col_idx[k]; - int idx1 = B.col_idx[k + 1]; + if(mask.isSet(k)) { + int idx0 = B.col_idx[k]; + int idx1 = B.col_idx[k + 1]; - double sum = semiRing.add.id; - for (int indexB = idx0; indexB < idx1; indexB++) { - sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(a[offsetA + B.nz_rows[indexB]], B.nz_values[indexB])); + + double sum = semiRing.add.id; + for (int indexB = idx0; indexB < idx1; indexB++) { + sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(a[B.nz_rows[indexB]], B.nz_values[indexB])); + } + c[k] = sum; } - c[offsetC + k] = sum; } } public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing) { - mult(a, 0, B, c, 0, semiRing); + mult(a, B, c, semiRing, null); } /** diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java index 11c3d19d1..4301b63e3 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/misc/TestImplCommonOpsWithSemiRing_DSCC.java @@ -46,7 +46,7 @@ public void add(DSemiRing semiRing, double[] expected) { b.set(1, 1, 3); b.set(0, 0, 4); - ImplCommonOpsWithSemiRing_DSCC.add(1, a, 1, b, c, semiRing, null, null); + ImplCommonOpsWithSemiRing_DSCC.add(1, a, 1, b, c, semiRing, null, null, null); double[] found = new double[]{c.get(0, 0), c.get(1, 1)}; assertTrue(c.getNumElements() == 2); @@ -72,7 +72,7 @@ public void testelementWiseMult(DSemiRing semiRing, double[] expected) { DMatrixSparseCSC result = new DMatrixSparseCSC(3, 3, 0); - ImplCommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, result, semiRing, null, null); + ImplCommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, result, semiRing, null, null, null); assertEquals(2, result.getNumElements()); assertTrue(expected[0] == result.get(1, 1)); diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java index c5510f96b..b22d7b638 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java @@ -65,7 +65,7 @@ public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { vector.set(0, 3, 0.5); vector.set(0, 5, 0.6); - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, null); assertEquals(expected[0], found.get(0, 0)); assertEquals(expected[1], found.get(0, 2)); From 68d5fbfb567315ec1123700198101c37248e6403 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 16:15:08 +0200 Subject: [PATCH 22/48] Check if mask is null in MatrixVectorMult --- .../sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java index 5c8ea2ae9..291ed4aef 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java @@ -42,7 +42,7 @@ public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing se Arrays.fill(c, semiRing.add.id); } else { for (int i = 0; i < c.length; i++) { - if (mask.isSet(i)) { + if (mask == null || mask.isSet(i)) { c[i] = semiRing.add.id; } } @@ -88,7 +88,7 @@ public static void multAdd(DMatrixSparseCSC A, double[] b, double[] c, DSemiRing */ public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing, @Nullable PrimitiveDMask mask) { for (int k = 0; k < B.numCols; k++) { - if(mask.isSet(k)) { + if(mask == null || mask.isSet(k)) { int idx0 = B.col_idx[k]; int idx1 = B.col_idx[k + 1]; From f6d8e5c1491aa25a02eabda82a0338490e35413c Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 16:16:11 +0200 Subject: [PATCH 23/48] Extract @Setup method in base test class --- ...eTestMatrixMatrixOpsWithSemiRing_DSCC.java | 45 +++++++++++++++++++ ...TestMatrixMatrixMultWithSemiRing_DSCC.java | 23 +--------- 2 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 main/ejml-dsparse/test/org/ejml/sparse/csc/mult/BaseTestMatrixMatrixOpsWithSemiRing_DSCC.java diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/BaseTestMatrixMatrixOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/BaseTestMatrixMatrixOpsWithSemiRing_DSCC.java new file mode 100644 index 000000000..8c458278a --- /dev/null +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/BaseTestMatrixMatrixOpsWithSemiRing_DSCC.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.mult; + +import org.ejml.data.DMatrixSparseCSC; +import org.junit.jupiter.api.BeforeEach; + +public class BaseTestMatrixMatrixOpsWithSemiRing_DSCC { + DMatrixSparseCSC inputMatrix; + + @BeforeEach + public void setUp() { + // based on example in http://mit.bme.hu/~szarnyas/grb/graphblas-introduction.pdf + inputMatrix = new DMatrixSparseCSC(7, 7, 12); + inputMatrix.set(0, 1, 1); + inputMatrix.set(0, 3, 1); + inputMatrix.set(1, 4, 1); + inputMatrix.set(1, 6, 1); + inputMatrix.set(2, 5, 1); + inputMatrix.set(3, 0, 0.2); + inputMatrix.set(3, 2, 0.4); + inputMatrix.set(4, 5, 1); + inputMatrix.set(5, 2, 0.5); + inputMatrix.set(6, 2, 1); + inputMatrix.set(6, 3, 1); + inputMatrix.set(6, 4, 1); + + } +} \ No newline at end of file diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java index b22d7b638..d8394ef59 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java @@ -35,28 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class TestMatrixMatrixMultWithSemiRing_DSCC { - DMatrixSparseCSC inputMatrix; - - @BeforeEach - public void setUp() { - // based on example in http://mit.bme.hu/~szarnyas/grb/graphblas-introduction.pdf - inputMatrix = new DMatrixSparseCSC(7, 7, 12); - inputMatrix.set(0, 1, 1); - inputMatrix.set(0, 3, 1); - inputMatrix.set(1, 4, 1); - inputMatrix.set(1, 6, 1); - inputMatrix.set(2, 5, 1); - inputMatrix.set(3, 0, 0.2); - inputMatrix.set(3, 2, 0.4); - inputMatrix.set(4, 5, 1); - inputMatrix.set(5, 2, 0.5); - inputMatrix.set(6, 2, 1); - inputMatrix.set(6, 3, 1); - inputMatrix.set(6, 4, 1); - - } - +public class TestMatrixMatrixMultWithSemiRing_DSCC extends BaseTestMatrixMatrixOpsWithSemiRing_DSCC { @ParameterizedTest(name = "{0}") @MethodSource("sparseVectorMatrixMultSources") public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { From 2a6fffc411540073c296fbd599d11ebb70c1c729 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 16:16:40 +0200 Subject: [PATCH 24/48] Start testing masked operations --- .../csc/mult/TestMaskedOperators_DSCC.java | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java new file mode 100644 index 000000000..d6726fbdc --- /dev/null +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.mult; + +import org.ejml.data.DMatrixSparse; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.masks.DMasks; +import org.ejml.masks.Mask; +import org.ejml.masks.PrimitiveDMask; +import org.ejml.ops.DSemiRing; +import org.ejml.ops.DSemiRings; +import org.ejml.sparse.csc.CommonOpsWithSemiRing_DSCC; +import org.ejml.sparse.csc.CommonOps_DSCC; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Iterator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// TODO: proper base test that includes the matrix +public class TestMaskedOperators_DSCC extends BaseTestMatrixMatrixOpsWithSemiRing_DSCC { + + DSemiRing semiRing = DSemiRings.PLUS_TIMES; + + @Test + public void mult_v_A() { + + // graphblas == following outgoing edges of source nodes + double v[] = new double[7]; + Arrays.fill(v, semiRing.add.id); + v[3] = 0.5; + v[0] = 0.6; + + System.out.println("input vector = " + Arrays.toString(v)); + + double[] found = new double[7]; + double[] foundWithMask = found.clone(); + + // == dont calculate for existing entries (currently still zero-ing out very likely) + PrimitiveDMask mask = new PrimitiveDMask(v, true); + + MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, found, semiRing); + MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, foundWithMask, semiRing, mask); + + assertMaskedResult(found, foundWithMask, mask); + } + + @Test + public void mult_A_v() { + // graphblas == following incoming edges of source nodes + double[] v = new double[7]; + Arrays.fill(v, semiRing.add.id); + v[3] = 0.5; + v[4] = 0.6; + + double[] found = new double[7]; + double[] foundWithMask = found.clone(); + + // == dont calculate for existing entries (currently still zero-ing out very likely) + PrimitiveDMask mask = new PrimitiveDMask(v, true); + + MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, found, semiRing); + MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, foundWithMask, semiRing, mask); + + assertMaskedResult(found, foundWithMask, mask); + } + + // matrix, matrix ops + // TODO refactor common parts & parameterize masks + + @Test + public void mult_A_B() { + // graphblas == following outgoing edges of source nodes + DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); + vector.set(0, 3, 0.5); + vector.set(0, 5, 0.6); + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, null); + + // TODO: parameterize test + boolean negated = true; + boolean structural = true; + Mask mask = DMasks.of(vector, negated, structural); + + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, mask); + + assertMaskedResult(found, foundWithMask, mask); + } + + @Test + public void mult_Transposed_A_B() { + // graphblas == following outgoing edges of source nodes + DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); + vector.set(0, 3, 0.5); + vector.set(0, 5, 0.6); + + DMatrixSparseCSC transposed_vector = CommonOps_DSCC.transpose(vector, null, null); + DMatrixSparseCSC transposed_matrix = CommonOps_DSCC.transpose(inputMatrix, null, null); + + DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, null, semiRing, null, null, null); + DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, null, null, null); + + boolean negated = true; + boolean structural = true; + Mask mask = DMasks.of(vector, negated, structural); + + DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, null, semiRing, mask, null, null); + DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, mask, null, null); + + assertMaskedResult(foundTA, foundTAWithMask, mask); + assertMaskedResult(foundTB, foundTBWithMask, mask); + } + + @Test + public void add_A_B() { + // graphblas == following outgoing edges of source nodes + DMatrixSparseCSC vector = new DMatrixSparseCSC(7, 7); + vector.set(0, 3, 0.5); + vector.set(0, 5, 0.6); + + DMatrixSparseCSC found = new DMatrixSparseCSC(0, 0); + DMatrixSparseCSC foundWithMask = found.createLike(); + + CommonOpsWithSemiRing_DSCC.add(1, vector, 1, inputMatrix, found, semiRing, null, null, null); + + // TODO: parameterize test + boolean negated = true; + boolean structural = true; + Mask mask = DMasks.of(vector, negated, structural); + + CommonOpsWithSemiRing_DSCC.add(1, vector, 1, inputMatrix, foundWithMask, semiRing, mask, null, null); + + System.out.println("found"); + found.print(); + + System.out.println("foundWithMask"); + foundWithMask.print(); + + assertMaskedResult(found, foundWithMask, mask); + } + + // TODO: test elementWise-Mult, apply and reduce + + + + + + // TODO: assert that foundWithMask equals entry in input matrix if mask.isSet(row, col) + // otherwise check for value.value equeals foundWithMask.get(row, col) + private void assertMaskedResult(DMatrixSparseCSC found, DMatrixSparseCSC foundWithMask, Mask mask) { + Iterator it = found.createCoordinateIterator(); + it.forEachRemaining(value -> assertEquals(mask.isSet(value.row, value.col), foundWithMask.isAssigned(value.row, value.col))); + } + + private void assertMaskedResult(double[] found, double[] foundWithMask, PrimitiveDMask mask) { + for (int i = 0; i < found.length; i++) { + if (mask.isSet(i)) { + assertTrue(found[i] == foundWithMask[i]); + } + else { + // at some point this should be v[i] == foundWithMask[i] + assertEquals(semiRing.add.id, foundWithMask[i]); + } + } + } +} \ No newline at end of file From ea25bbfd7e2161de5b53599f8197bf92be266677 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 17:14:34 +0200 Subject: [PATCH 25/48] Fix after rebase --- .../org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java | 6 ++++-- .../csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index f4e563d18..05765f7ad 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -246,13 +246,15 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, + public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); output = reshapeOrDeclare(output, A, A.numRows, A.numCols); - ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, C, semiRing, mask, gw, gx); + ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, mask, gw, gx); + + return output; } } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java index d8394ef59..77a76d2fc 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java @@ -55,7 +55,7 @@ public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { public void mult_AT_B(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransA(matrix, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransA(matrix, otherMatrix, null, semiRing, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.multTransA(matrix, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); @@ -66,7 +66,7 @@ public void mult_AT_B(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC oth public void mult_A_BT(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransB(matrix, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransB(matrix, otherMatrix, null, semiRing, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.multTransB(matrix, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); @@ -77,7 +77,7 @@ public void mult_A_BT(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC oth public void elementMult(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, null, semiRing, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.elementMult(matrix, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); @@ -88,7 +88,7 @@ public void elementMult(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC o public void add(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, matrix, 1, otherMatrix, null, semiRing, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, matrix, 1, otherMatrix, null, semiRing, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.add(1, matrix, 1, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); From 49e07e8f7bd15a07c539b13e057e2bc7ca251afb Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 17:39:48 +0200 Subject: [PATCH 26/48] Fix off by one error for corresponding result cell --- .../sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 5e2a97899..dbf8884eb 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -128,7 +128,7 @@ public static void multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSpa // C(colA,colB) = A(:,colA)*B(:,colB) for (int colA = 0; colA < A.numCols; colA++) { - if (mask == null || mask.isSet(colA, bj)) { + if (mask == null || mask.isSet(colA, bj - 1)) { int idxA0 = A.col_idx[colA]; int idxA1 = A.col_idx[colA + 1]; From bb68c4046c3b1f4a153c7464072c1a620f3ced5a Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 17:40:31 +0200 Subject: [PATCH 27/48] More testing --- .../csc/mult/TestMaskedOperators_DSCC.java | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index d6726fbdc..0fbcd1f6c 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -106,29 +106,46 @@ public void mult_A_B() { } @Test - public void mult_Transposed_A_B() { + public void mult_T_A_B() { // graphblas == following outgoing edges of source nodes DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); vector.set(0, 3, 0.5); vector.set(0, 5, 0.6); DMatrixSparseCSC transposed_vector = CommonOps_DSCC.transpose(vector, null, null); - DMatrixSparseCSC transposed_matrix = CommonOps_DSCC.transpose(inputMatrix, null, null); DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, null, semiRing, null, null, null); - DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, null, null, null); boolean negated = true; boolean structural = true; Mask mask = DMasks.of(vector, negated, structural); DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, null, semiRing, mask, null, null); - DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, mask, null, null); assertMaskedResult(foundTA, foundTAWithMask, mask); + } + + @Test + public void mult_A_T_B() { + // graphblas == following outgoing edges of source nodes + DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); + vector.set(0, 3, 0.5); + vector.set(0, 5, 0.6); + + DMatrixSparseCSC transposed_matrix = CommonOps_DSCC.transpose(inputMatrix, null, null); + + DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, null, null, null); + + boolean negated = true; + boolean structural = true; + Mask mask = DMasks.of(vector, negated, structural); + + DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, mask, null, null); + assertMaskedResult(foundTB, foundTBWithMask, mask); } + @Test public void add_A_B() { // graphblas == following outgoing edges of source nodes @@ -161,21 +178,24 @@ public void add_A_B() { - - - // TODO: assert that foundWithMask equals entry in input matrix if mask.isSet(row, col) - // otherwise check for value.value equeals foundWithMask.get(row, col) private void assertMaskedResult(DMatrixSparseCSC found, DMatrixSparseCSC foundWithMask, Mask mask) { Iterator it = found.createCoordinateIterator(); - it.forEachRemaining(value -> assertEquals(mask.isSet(value.row, value.col), foundWithMask.isAssigned(value.row, value.col))); + it.forEachRemaining(value -> { + if (mask.isSet(value.row, value.col)) { + assertEquals(found.get(value.row, value.col), foundWithMask.get(value.row, value.col)); + } + else { + // TODO: check if it is the value of the input result matrix (currently just overwritten) + assertEquals(semiRing.add.id, foundWithMask.get(value.row, value.col)); + } + }); } private void assertMaskedResult(double[] found, double[] foundWithMask, PrimitiveDMask mask) { for (int i = 0; i < found.length; i++) { if (mask.isSet(i)) { - assertTrue(found[i] == foundWithMask[i]); - } - else { + assertEquals(foundWithMask[i], found[i]); + } else { // at some point this should be v[i] == foundWithMask[i] assertEquals(semiRing.add.id, foundWithMask[i]); } From 40f9564821e70cc0a0082f1180c870c9f8917b40 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 4 Aug 2020 17:43:01 +0200 Subject: [PATCH 28/48] And another off by one error in calling mask.isSet --- .../sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java | 2 +- .../test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index dbf8884eb..6bb9243f7 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -79,7 +79,7 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC for (int i = idxC0; i < idxC1; i++) { // this will destroy simdi usage here .. (hopefully not if mask == null) - if (mask == null || mask.isSet(C.nz_rows[i], bj)) { + if (mask == null || mask.isSet(C.nz_rows[i], bj - 1)) { C.nz_values[i] = x[C.nz_rows[i]]; } } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 0fbcd1f6c..8a286ffd6 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -91,7 +91,7 @@ public void mult_A_B() { // graphblas == following outgoing edges of source nodes DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); vector.set(0, 3, 0.5); - vector.set(0, 5, 0.6); + vector.set(0, 4, 0.6); DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, null); From d1d9fbef2c314e723bcf7106590a96f7a0efa7cd Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 6 Aug 2020 11:53:22 +0200 Subject: [PATCH 29/48] Dont overwrite existing result fields if mask is not set on this field for simple `mult()` .. need to combine cached prevResult and result at the end of the op (previous problem was that the computation overwrites the whole matrix structure -> thus also fields that were not even tried to be computed) --- main/ejml-core/src/org/ejml/UtilEjml.java | 9 ++ .../csc/CommonOpsWithSemiRing_DSCC.java | 35 +++++- .../misc/ImplCommonOpsWithSemiRing_DSCC.java | 38 ++++++ ...ImplSparseSparseMultWithSemiRing_DSCC.java | 34 +++++- .../csc/mult/TestMaskedOperators_DSCC.java | 113 +++++++++++------- 5 files changed, 177 insertions(+), 52 deletions(-) diff --git a/main/ejml-core/src/org/ejml/UtilEjml.java b/main/ejml-core/src/org/ejml/UtilEjml.java index 7d23b60d8..ec735e1ce 100644 --- a/main/ejml-core/src/org/ejml/UtilEjml.java +++ b/main/ejml-core/src/org/ejml/UtilEjml.java @@ -22,6 +22,7 @@ import org.ejml.interfaces.linsol.LinearSolver; import org.ejml.interfaces.linsol.LinearSolverDense; import org.ejml.interfaces.linsol.LinearSolverSparse; +import org.ejml.masks.Mask; import org.ejml.ops.ConvertDMatrixStruct; import org.ejml.ops.ConvertFMatrixStruct; @@ -549,4 +550,12 @@ public static boolean hasNullableArgument(Method func) { Annotation last = lastArray[lastArray.length-1]; return last.toString().contains("Nullable"); } + + /** + * Check if output matrix needs to be cached for later merge with actual result + * (in order to prevent deleting entries which shouldnt be overwritten, e.g. !mask.isSet()) + */ + public static boolean useInitialOutput(Mask mask, Matrix output, int numRows, int numCols) { + return mask != null && output != null && output.getNumRows() == numRows && output.getNumCols() == numCols; + } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 05765f7ad..8b98eef46 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -19,11 +19,13 @@ package org.ejml.sparse.csc; import org.ejml.MatrixDimensionException; +import org.ejml.UtilEjml; import org.ejml.data.DGrowArray; import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; import org.ejml.masks.Mask; +import org.ejml.ops.DMonoid; import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; import org.ejml.sparse.csc.mult.ImplSparseSparseMultWithSemiRing_DSCC; @@ -55,10 +57,27 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - output = reshapeOrDeclare(output,A,A.numRows,B.numCols); + // !! important to do before reshape + DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numCols) ? output.copy() : null; + + + output = reshapeOrDeclare(output,A,A.numRows,B.numCols); ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, mask, gw, gx); + return combineOutputs(output, semiRing.add, initialOutput); + } + + // for applying mask and accumulator + private static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DMonoid accum, DMatrixSparseCSC initialOutput) { + if (initialOutput != null) { + // memory overhead .. maybe also can reuse something? + DMatrixSparseCSC combinedOutput = output.createLike(); + // instead of "semiRing.add" this could be a dedicated accumulator + ImplCommonOpsWithSemiRing_DSCC.add(initialOutput, output, combinedOutput, accum, null, null); + // is the previous result of C gc-able? (should be) + output = combinedOutput; + } return output; } @@ -66,11 +85,13 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + // !! important to do before reshape + DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numCols, B.numCols) ? output.copy() : null; output = reshapeOrDeclare(output,A,A.numCols,B.numCols); ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, mask, gw, gx); - return output; + return combineOutputs(output, semiRing.add, initialOutput); } /** @@ -88,10 +109,11 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numRows) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, B.numRows); if (!B.isIndicesSorted()) B.sortIndices(null); ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, mask, gw, gx); - return output; + return combineOutputs(output, semiRing.add, initialOutput); } @@ -211,6 +233,7 @@ public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj ImplSparseSparseMultWithSemiRing_DSCC.multAddTransAB(A, B, C, semiRing); } + // sparse versions again /** * Performs matrix addition:
* output = αA + βB @@ -228,11 +251,12 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows || A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); + DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, A.numCols); ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, mask, gw, gx); - return output; + return combineOutputs(output, semiRing.add, initialOutput); } /** @@ -250,11 +274,12 @@ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); + DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, A.numCols); ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, mask, gw, gx); - return output; + return combineOutputs(output, semiRing.add, initialOutput); } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index 0a5a4b06c..9b6bac988 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -22,6 +22,7 @@ import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; import org.ejml.masks.Mask; +import org.ejml.ops.DMonoid; import org.ejml.ops.DSemiRing; import javax.annotation.Nullable; @@ -74,6 +75,43 @@ public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSpa C.col_idx[A.numCols] = C.nz_length; } + /** + * Performs matrix addition:
+ * C = A + B + * + * .. f.i. to combine intialOutput and computedOutput (needed for masks and accumulators in GraphHBLAS) + * @param A Matrix + * @param B Matrix + * @param C Output matrix. + * @param accum accumulator + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DMonoid accum, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows, A.numRows); + + C.indicesSorted = false; + C.nz_length = 0; + + for (int col = 0; col < A.numCols; col++) { + C.col_idx[col] = C.nz_length; + + multAddColA(A, col, C, col + 1, accum, x, w); + multAddColA(B, col, C, col + 1, accum, x, w); + + // take the values in the dense vector 'x' and put them into 'C' + int idxC0 = C.col_idx[col]; + int idxC1 = C.col_idx[col + 1]; + + for (int i = idxC0; i < idxC1; i++) { + C.nz_values[i] = x[C.nz_rows[i]]; + } + } + C.col_idx[A.numCols] = C.nz_length; + } + /** * Adds the results of adding a column in A and B as a new column in C.
* C(:,end+1) = A(:,colA) + B(:,colB) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 6bb9243f7..9d294cdfe 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -23,8 +23,10 @@ import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; import org.ejml.masks.Mask; +import org.ejml.ops.DMonoid; import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.CommonOps_DSCC; +import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; import javax.annotation.Nullable; @@ -46,7 +48,7 @@ public class ImplSparseSparseMultWithSemiRing_DSCC { * @param gx (Optional) Storage for internal workspace. Can be null. */ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DSemiRing semiRing, @Nullable Mask mask, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -86,7 +88,6 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC idx0 = idx1; } - } /** @@ -244,6 +245,35 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, } } + /** + * Performs the performing operation x = x + A(:,i) + * for applying a accumulator + */ + public static void multAddColA(DMatrixSparseCSC A, int colA, + DMatrixSparseCSC C, int mark, + DMonoid accum, + double x[], int w[]) { + int idxA0 = A.col_idx[colA]; + int idxA1 = A.col_idx[colA + 1]; + + for (int j = idxA0; j < idxA1; j++) { + int row = A.nz_rows[j]; + + if (w[row] < mark) { + if (C.nz_length >= C.nz_rows.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } + + w[row] = mark; + C.nz_rows[C.nz_length] = row; + C.col_idx[mark] = ++C.nz_length; + x[row] = A.nz_values[j]; + } else { + x[row] = accum.func.apply(x[row], A.nz_values[j]); + } + } + } + // sparse-dense variants public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 8a286ffd6..8bd5993cb 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -35,7 +35,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -// TODO: proper base test that includes the matrix public class TestMaskedOperators_DSCC extends BaseTestMatrixMatrixOpsWithSemiRing_DSCC { DSemiRing semiRing = DSemiRings.PLUS_TIMES; @@ -49,18 +48,19 @@ public void mult_v_A() { v[3] = 0.5; v[0] = 0.6; - System.out.println("input vector = " + Arrays.toString(v)); - - double[] found = new double[7]; - double[] foundWithMask = found.clone(); + double[] prevResult = new double[7]; + prevResult[3] = 99; + prevResult[0] = 42; + double[] found = prevResult.clone(); + double[] foundWithMask = prevResult.clone(); // == dont calculate for existing entries (currently still zero-ing out very likely) - PrimitiveDMask mask = new PrimitiveDMask(v, true); + PrimitiveDMask mask = new PrimitiveDMask(prevResult, true); MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, found, semiRing); MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, foundWithMask, semiRing, mask); - assertMaskedResult(found, foundWithMask, mask); + assertMaskedResult(prevResult, found, foundWithMask, mask); } @Test @@ -71,16 +71,19 @@ public void mult_A_v() { v[3] = 0.5; v[4] = 0.6; - double[] found = new double[7]; - double[] foundWithMask = found.clone(); + double[] prevResult = new double[7]; + prevResult[3] = 99; + prevResult[0] = 42; + double[] found = prevResult.clone(); + double[] foundWithMask = prevResult.clone(); // == dont calculate for existing entries (currently still zero-ing out very likely) - PrimitiveDMask mask = new PrimitiveDMask(v, true); + PrimitiveDMask mask = new PrimitiveDMask(prevResult, true); MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, found, semiRing); MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, foundWithMask, semiRing, mask); - assertMaskedResult(found, foundWithMask, mask); + assertMaskedResult(prevResult, found, foundWithMask, mask); } // matrix, matrix ops @@ -93,16 +96,19 @@ public void mult_A_B() { vector.set(0, 3, 0.5); vector.set(0, 4, 0.6); - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, null); + DMatrixSparseCSC prevResult = vector.copy(); + prevResult.set(0, 2, 99); + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, prevResult.copy(), semiRing, null); - // TODO: parameterize test + // TODO: parameterize test .. also changes expected result boolean negated = true; boolean structural = true; - Mask mask = DMasks.of(vector, negated, structural); + Mask mask = DMasks.of(prevResult, negated, structural); - DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, mask); + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, prevResult.copy(), semiRing, mask); - assertMaskedResult(found, foundWithMask, mask); + assertMaskedResult(prevResult, found, foundWithMask, mask); } @Test @@ -112,58 +118,69 @@ public void mult_T_A_B() { vector.set(0, 3, 0.5); vector.set(0, 5, 0.6); + DMatrixSparseCSC prevResult = vector.copy(); + prevResult.set(0, 2, 99); + + DMatrixSparseCSC transposed_vector = CommonOps_DSCC.transpose(vector, null, null); - DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, null, semiRing, null, null, null); + DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, prevResult.copy(), semiRing, null, null, null); boolean negated = true; boolean structural = true; - Mask mask = DMasks.of(vector, negated, structural); + Mask mask = DMasks.of(prevResult, negated, structural); - DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, null, semiRing, mask, null, null); + DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, prevResult.copy(), semiRing, mask, null, null); - assertMaskedResult(foundTA, foundTAWithMask, mask); + assertMaskedResult(prevResult, foundTA, foundTAWithMask, mask); } @Test public void mult_A_T_B() { // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); - vector.set(0, 3, 0.5); - vector.set(0, 5, 0.6); + DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); + inputVector.set(0, 3, 0.5); + inputVector.set(0, 5, 0.6); DMatrixSparseCSC transposed_matrix = CommonOps_DSCC.transpose(inputMatrix, null, null); - DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, null, null, null); + DMatrixSparseCSC prevResult = inputVector.copy(); + prevResult.set(0, 2, 99); + + DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB(inputVector, transposed_matrix, prevResult.copy(), semiRing, null, null, null); boolean negated = true; boolean structural = true; - Mask mask = DMasks.of(vector, negated, structural); + Mask mask = DMasks.of(prevResult, negated, structural); - DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB(vector, transposed_matrix, null, semiRing, mask, null, null); + DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB(inputVector, transposed_matrix, prevResult.copy(), semiRing, mask, null, null); - assertMaskedResult(foundTB, foundTBWithMask, mask); + assertMaskedResult(prevResult, foundTB, foundTBWithMask, mask); } @Test public void add_A_B() { // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC vector = new DMatrixSparseCSC(7, 7); - vector.set(0, 3, 0.5); - vector.set(0, 5, 0.6); + DMatrixSparseCSC otherMatrix = new DMatrixSparseCSC(7, 7); + otherMatrix.set(0, 3, 0.5); + otherMatrix.set(0, 5, 0.6); - DMatrixSparseCSC found = new DMatrixSparseCSC(0, 0); - DMatrixSparseCSC foundWithMask = found.createLike(); + DMatrixSparseCSC resultInput = new DMatrixSparseCSC(7, 7); + // these should not be kept in the result (as negated mask) + resultInput.set(0,0, 99); + resultInput.set(0,3, 42); - CommonOpsWithSemiRing_DSCC.add(1, vector, 1, inputMatrix, found, semiRing, null, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, otherMatrix, 1, inputMatrix, resultInput.copy(), semiRing, null, null, null); - // TODO: parameterize test boolean negated = true; boolean structural = true; - Mask mask = DMasks.of(vector, negated, structural); + Mask mask = DMasks.of(resultInput, negated, structural); + + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.add(1, otherMatrix, 1, inputMatrix, resultInput.copy(), semiRing, mask, null, null); - CommonOpsWithSemiRing_DSCC.add(1, vector, 1, inputMatrix, foundWithMask, semiRing, mask, null, null); + System.out.println("resultInput"); + resultInput.print(); System.out.println("found"); found.print(); @@ -171,33 +188,39 @@ public void add_A_B() { System.out.println("foundWithMask"); foundWithMask.print(); - assertMaskedResult(found, foundWithMask, mask); + assertMaskedResult(resultInput, found, foundWithMask, mask); } // TODO: test elementWise-Mult, apply and reduce - private void assertMaskedResult(DMatrixSparseCSC found, DMatrixSparseCSC foundWithMask, Mask mask) { + private void assertMaskedResult(DMatrixSparseCSC prevResult, DMatrixSparseCSC found, DMatrixSparseCSC foundWithMask, Mask mask) { Iterator it = found.createCoordinateIterator(); + // check that existing result were not overwritten it.forEachRemaining(value -> { if (mask.isSet(value.row, value.col)) { - assertEquals(found.get(value.row, value.col), foundWithMask.get(value.row, value.col)); + assertEquals(found.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field should have been computed"); } else { - // TODO: check if it is the value of the input result matrix (currently just overwritten) - assertEquals(semiRing.add.id, foundWithMask.get(value.row, value.col)); + assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was overwritten"); + } + }); + + // checking that untouched cells are still present + prevResult.createCoordinateIterator().forEachRemaining(value -> { + if (!mask.isSet(value.row, value.col)) { + assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was deleted"); } }); } - private void assertMaskedResult(double[] found, double[] foundWithMask, PrimitiveDMask mask) { + private void assertMaskedResult(double[] prevResult, double[] found, double[] foundWithMask, PrimitiveDMask mask) { for (int i = 0; i < found.length; i++) { if (mask.isSet(i)) { - assertEquals(foundWithMask[i], found[i]); + assertEquals(foundWithMask[i], found[i], "Computation differs"); } else { - // at some point this should be v[i] == foundWithMask[i] - assertEquals(semiRing.add.id, foundWithMask[i]); + assertEquals(prevResult[i], foundWithMask[i], "Initial result was overwritten"); } } } From 15cc9bec722fca40e18ea379ae49ff9cd4afe6c7 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 6 Aug 2020 13:25:10 +0200 Subject: [PATCH 30/48] Refactor mask related operators into extra file --- .../csc/CommonOpsWithSemiRing_DSCC.java | 24 +-- .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 138 ++++++++++++++++++ .../misc/ImplCommonOpsWithSemiRing_DSCC.java | 38 ----- ...ImplSparseSparseMultWithSemiRing_DSCC.java | 31 ---- 4 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 8b98eef46..da06f60ca 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -34,6 +34,7 @@ import static org.ejml.UtilEjml.reshapeOrDeclare; import static org.ejml.UtilEjml.stringShapes; +import static org.ejml.sparse.csc.MaskUtil_DSCC.combineOutputs; public class CommonOpsWithSemiRing_DSCC { @@ -65,20 +66,7 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul output = reshapeOrDeclare(output,A,A.numRows,B.numCols); ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add, initialOutput); - } - - // for applying mask and accumulator - private static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DMonoid accum, DMatrixSparseCSC initialOutput) { - if (initialOutput != null) { - // memory overhead .. maybe also can reuse something? - DMatrixSparseCSC combinedOutput = output.createLike(); - // instead of "semiRing.add" this could be a dedicated accumulator - ImplCommonOpsWithSemiRing_DSCC.add(initialOutput, output, combinedOutput, accum, null, null); - // is the previous result of C gc-able? (should be) - output = combinedOutput; - } - return output; + return combineOutputs(output, semiRing.add.func, initialOutput); } public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, @@ -91,7 +79,7 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add, initialOutput); + return combineOutputs(output, semiRing.add.func, initialOutput); } /** @@ -113,7 +101,7 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B output = reshapeOrDeclare(output, A, A.numRows, B.numRows); if (!B.isIndicesSorted()) B.sortIndices(null); ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add, initialOutput); + return combineOutputs(output, semiRing.add.func, initialOutput); } @@ -256,7 +244,7 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add, initialOutput); + return combineOutputs(output, semiRing.add.func, initialOutput); } /** @@ -279,7 +267,7 @@ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add, initialOutput); + return combineOutputs(output, semiRing.add.func, initialOutput); } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java new file mode 100644 index 000000000..d1e89a850 --- /dev/null +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc; + +import org.ejml.data.DGrowArray; +import org.ejml.data.DMatrixRMaj; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; +import org.ejml.masks.Mask; +import org.ejml.ops.DBinaryOperator; + +import javax.annotation.Nullable; + +import static org.ejml.UtilEjml.adjust; +import static org.ejml.UtilEjml.checkSameShape; + +public class MaskUtil_DSCC { + // for applying mask and accumulator (output gets overwritten) + static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { + if (initialOutput != null) { + // memory overhead .. maybe also can reuse something? + DMatrixSparseCSC combinedOutput = output.createLike(); + // instead of "semiRing.add" this could be a dedicated accumulator + add(initialOutput, output, combinedOutput, accum, null, null); + // is the previous result of C gc-able? (should be) + output = combinedOutput; + } + return output; + } + + static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, Mask mask, @Nullable DBinaryOperator accum) { + checkSameShape(initialOutput, output, true); + + // TODO: operate on a bitset/boolean[] here -> also just one for-loop needed + for (int col = 0; col < output.getNumCols(); col++) { + for (int row = 0; row < output.numRows; row++) { + if (mask.isSet(row, col)) { + if (accum != null) { + // combine previous value and computed value + output.unsafe_set(row, col, accum.apply(output.get(row, col), initialOutput.get(row, col))); + } + // else output value just keeps as it is + } + else { + // just use previous value as it shouldnt be computed in the first place + output.unsafe_set(row, col, initialOutput.get(row, col)); + } + } + } + return output; + } + + /** + * Performs matrix addition:
+ * C = A + B + * + * .. f.i. to combine intialOutput and computedOutput (needed for masks and accumulators in GraphHBLAS) + * @param A Matrix + * @param B Matrix + * @param C Output matrix. + * @param accum accumulator + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. + */ + public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DBinaryOperator accum, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + double[] x = adjust(gx, A.numRows); + int[] w = adjust(gw, A.numRows, A.numRows); + + C.indicesSorted = false; + C.nz_length = 0; + + for (int col = 0; col < A.numCols; col++) { + C.col_idx[col] = C.nz_length; + + // always take the values of A + // second as x[row] would be the first argument + multAddColA(A, col, C, col + 1, (a, b) -> b, x, w); + // accum values of A and B + multAddColA(B, col, C, col + 1, accum, x, w); + + // take the values in the dense vector 'x' and put them into 'C' + int idxC0 = C.col_idx[col]; + int idxC1 = C.col_idx[col + 1]; + + for (int i = idxC0; i < idxC1; i++) { + C.nz_values[i] = x[C.nz_rows[i]]; + } + } + C.col_idx[A.numCols] = C.nz_length; + } + + /** + * Performs the performing operation x = x + A(:,i) + * for applying a accumulator + */ + public static void multAddColA(DMatrixSparseCSC A, int colA, + DMatrixSparseCSC C, int mark, + DBinaryOperator accum, + double x[], int w[]) { + int idxA0 = A.col_idx[colA]; + int idxA1 = A.col_idx[colA + 1]; + + for (int j = idxA0; j < idxA1; j++) { + int row = A.nz_rows[j]; + + if (w[row] < mark) { + if (C.nz_length >= C.nz_rows.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } + + w[row] = mark; + C.nz_rows[C.nz_length] = row; + C.col_idx[mark] = ++C.nz_length; + x[row] = A.nz_values[j]; + } else if (accum != null) { + // if it is null .. x[row] can just stay the same + x[row] = accum.apply(x[row], A.nz_values[j]); + } + } + } +} diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index 9b6bac988..0a5a4b06c 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -22,7 +22,6 @@ import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; import org.ejml.masks.Mask; -import org.ejml.ops.DMonoid; import org.ejml.ops.DSemiRing; import javax.annotation.Nullable; @@ -75,43 +74,6 @@ public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSpa C.col_idx[A.numCols] = C.nz_length; } - /** - * Performs matrix addition:
- * C = A + B - * - * .. f.i. to combine intialOutput and computedOutput (needed for masks and accumulators in GraphHBLAS) - * @param A Matrix - * @param B Matrix - * @param C Output matrix. - * @param accum accumulator - * @param gw (Optional) Storage for internal workspace. Can be null. - * @param gx (Optional) Storage for internal workspace. Can be null. - */ - public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DMonoid accum, - @Nullable IGrowArray gw, @Nullable DGrowArray gx) { - double[] x = adjust(gx, A.numRows); - int[] w = adjust(gw, A.numRows, A.numRows); - - C.indicesSorted = false; - C.nz_length = 0; - - for (int col = 0; col < A.numCols; col++) { - C.col_idx[col] = C.nz_length; - - multAddColA(A, col, C, col + 1, accum, x, w); - multAddColA(B, col, C, col + 1, accum, x, w); - - // take the values in the dense vector 'x' and put them into 'C' - int idxC0 = C.col_idx[col]; - int idxC1 = C.col_idx[col + 1]; - - for (int i = idxC0; i < idxC1; i++) { - C.nz_values[i] = x[C.nz_rows[i]]; - } - } - C.col_idx[A.numCols] = C.nz_length; - } - /** * Adds the results of adding a column in A and B as a new column in C.
* C(:,end+1) = A(:,colA) + B(:,colB) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 9d294cdfe..7b22ceced 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -23,10 +23,8 @@ import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; import org.ejml.masks.Mask; -import org.ejml.ops.DMonoid; import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.CommonOps_DSCC; -import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; import javax.annotation.Nullable; @@ -245,35 +243,6 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, } } - /** - * Performs the performing operation x = x + A(:,i) - * for applying a accumulator - */ - public static void multAddColA(DMatrixSparseCSC A, int colA, - DMatrixSparseCSC C, int mark, - DMonoid accum, - double x[], int w[]) { - int idxA0 = A.col_idx[colA]; - int idxA1 = A.col_idx[colA + 1]; - - for (int j = idxA0; j < idxA1; j++) { - int row = A.nz_rows[j]; - - if (w[row] < mark) { - if (C.nz_length >= C.nz_rows.length) { - C.growMaxLength(C.nz_length * 2 + 1, true); - } - - w[row] = mark; - C.nz_rows[C.nz_length] = row; - C.col_idx[mark] = ++C.nz_length; - x[row] = A.nz_values[j]; - } else { - x[row] = accum.func.apply(x[row], A.nz_values[j]); - } - } - } - // sparse-dense variants public static void mult(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj C, DSemiRing semiRing) { From 1ddd1a5fa17f24c4490b4f112bd2cd620c296ad6 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 6 Aug 2020 13:25:40 +0200 Subject: [PATCH 31/48] Add tests for `elementWiseMult` and `apply` --- .../csc/mult/TestMaskedOperators_DSCC.java | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 8bd5993cb..c8b00cbd2 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -23,6 +23,7 @@ import org.ejml.masks.DMasks; import org.ejml.masks.Mask; import org.ejml.masks.PrimitiveDMask; +import org.ejml.ops.DBinaryOperator; import org.ejml.ops.DSemiRing; import org.ejml.ops.DSemiRings; import org.ejml.sparse.csc.CommonOpsWithSemiRing_DSCC; @@ -33,7 +34,6 @@ import java.util.Iterator; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; public class TestMaskedOperators_DSCC extends BaseTestMatrixMatrixOpsWithSemiRing_DSCC { @@ -179,19 +179,53 @@ public void add_A_B() { DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.add(1, otherMatrix, 1, inputMatrix, resultInput.copy(), semiRing, mask, null, null); - System.out.println("resultInput"); - resultInput.print(); + assertMaskedResult(resultInput, found, foundWithMask, mask); + } + + @Test + public void elementWiseMult() { + // graphblas == following outgoing edges of source nodes + DMatrixSparseCSC otherMatrix = new DMatrixSparseCSC(7, 7); + otherMatrix.set(0, 3, 0.5); + otherMatrix.set(0, 5, 0.6); - System.out.println("found"); - found.print(); + DMatrixSparseCSC resultInput = new DMatrixSparseCSC(7, 7); + // these should not be kept in the result (as negated mask) + resultInput.set(0,0, 99); + resultInput.set(0,3, 42); + + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(otherMatrix, inputMatrix, resultInput.copy(), semiRing, null, null, null); + + boolean negated = true; + boolean structural = true; + Mask mask = DMasks.of(resultInput, negated, structural); - System.out.println("foundWithMask"); - foundWithMask.print(); + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.elementMult(otherMatrix, inputMatrix, resultInput.copy(), semiRing, mask, null, null); assertMaskedResult(resultInput, found, foundWithMask, mask); } - // TODO: test elementWise-Mult, apply and reduce + @Test + public void apply() { + DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); + inputVector.set(0, 3, 0.5); + inputVector.set(0, 5, 0.6); + + DMatrixSparseCSC prevResult = inputVector.copy(); + prevResult.set(0, 2, 99); + + boolean negated = true; + boolean structural = true; + Mask mask = DMasks.of(prevResult, negated, structural); + + DBinaryOperator first = (x, y) -> x; + DMatrixSparseCSC result = CommonOps_DSCC.apply(inputVector, a -> a * 2, prevResult.copy(), null, null); + DMatrixSparseCSC resultWithMask = CommonOps_DSCC.apply(inputVector, a -> a * 2, prevResult.copy(), mask, first); + + assertMaskedResult(prevResult, result, resultWithMask, mask); + } + + // TODO: test reduce as well as general test of MaskUtil From 82c13258df321f8fbc6af877bc111717fda078f2 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 6 Aug 2020 13:25:55 +0200 Subject: [PATCH 32/48] Add masks for apply and reduce --- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 64 +++++++++++++------ .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 29 +++++---- .../csc/mult/TestMaskedOperators_DSCC.java | 53 ++++++++++++++- 3 files changed, 110 insertions(+), 36 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index 3392902bf..19e786a3d 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -19,6 +19,7 @@ package org.ejml.sparse.csc; import org.ejml.MatrixDimensionException; +import org.ejml.UtilEjml; import org.ejml.data.DGrowArray; import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; @@ -26,6 +27,7 @@ import org.ejml.dense.row.CommonOps_DDRM; import org.ejml.interfaces.decomposition.LUSparseDecomposition_F64; import org.ejml.interfaces.linsol.LinearSolverSparse; +import org.ejml.masks.Mask; import org.ejml.ops.DBinaryOperator; import org.ejml.ops.DUnaryOperator; import org.ejml.sparse.FillReducing; @@ -36,8 +38,10 @@ import javax.annotation.Nullable; import java.util.Arrays; +import java.util.BitSet; import static org.ejml.UtilEjml.*; +import static org.ejml.sparse.csc.MaskUtil_DSCC.combineOutputs; /** * @author Peter Abeles @@ -1880,10 +1884,6 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func } else if (input != output) { output.copyStructure(input); } - // TODO: how to apply mask (space/time tradeoff) - // only compute value if mask.isSet -> no SIMDI usage possible - // compute in tmp matrix -> memory usage doubled - // also here Mask needs to be based on nz_value index for (int i = 0; i < input.nz_length; i++) { output.nz_values[i] = func.apply(input.nz_values[i]); @@ -1896,6 +1896,21 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func return apply(input, func, input); } + // !! FIXME: apply also calculates unneeded fields .. this can produce wrong results with another accumulator than "first" + public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func, + @Nullable DMatrixSparseCSC output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { + DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, input.numCols) ? output.copy() : null; + output = reshapeOrDeclare(output, input); + + // dont check for the mask here as the computation is too simple to get any improvements when skipping + // would more likely prohibit vectorisation + for (int i = 0; i < input.nz_length; i++) { + output.nz_values[i] = func.apply(input.nz_values[i]); + } + + return combineOutputs(output, accum, initialOutput); + } + /** * This accumulates the matrix values to a scalar value. * @@ -1939,12 +1954,10 @@ public static double reduceScalar(DMatrixSparseCSC input, DBinaryOperator func) * @param output output (Output) Vector, where result can be stored in * @return a column-vector, where v[i] == values of column i reduced to scalar based on `func` */ - public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, @Nullable DMatrixRMaj output) { - if (output == null) { - output = new DMatrixRMaj(1, input.numCols); - } else { - output.reshape(1, input.numCols); - } + public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, + @Nullable DMatrixRMaj output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { + DMatrixRMaj initialOutput = UtilEjml.useInitialOutput(mask, output, 1, input.numCols) ? output.copy() : null; + output = reshapeOrDeclare(output, 1, input.numCols); for (int col = 0; col < input.numCols; col++) { int start = input.col_idx[col]; @@ -1952,14 +1965,18 @@ public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initVa double acc = initValue; for (int i = start; i < end; i++) { + // (assumption) not checking for mask here as computation should be faster than check acc = func.apply(acc, input.nz_values[i]); } - // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) output.data[col] = acc; } - return output; + return combineOutputs(output, initialOutput, mask, accum); + } + + public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, @Nullable DMatrixRMaj output) { + return reduceColumnWise(input, initValue, func, output, null, null); } /** @@ -1978,26 +1995,31 @@ public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initVa * @param output output (Output) Vector, where result can be stored in * @return a row-vector, where v[i] == values of row i reduced to scalar based on `func` */ - public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, @Nullable DMatrixRMaj output) { - if (output == null) { - output = new DMatrixRMaj(1, input.numRows); - } else { - output.reshape(1, input.numCols); - } - // TODO: allow optional resultAccumulator function (use tmp_result array to save reduce result and than combine arrays f.i. 2nd func) - // TODO: use tmp array and than set to output (there regard mask) + public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, + @Nullable DMatrixRMaj output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { + DMatrixRMaj initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, 1) ? output.copy() : null; + output = reshapeOrDeclare(output, input.numRows, 1); + Arrays.fill(output.data, initValue); + // TODO here it might be cheaper to have a bitset for the mask as isSet() gets called multiple times for each entry + //boolean[] isAssigned = mask.createBitSet(); + for (int col = 0; col < input.numCols; col++) { int start = input.col_idx[col]; int end = input.col_idx[col + 1]; for (int i = start; i < end; i++) { + // (assumption) not checking for mask here as computation should be faster than check output.data[input.nz_rows[i]] = func.apply(output.data[input.nz_rows[i]], input.nz_values[i]); } } - return output; + return combineOutputs(output, initialOutput, mask, accum); + } + + public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, @Nullable DMatrixRMaj output) { + return reduceRowWise(input, initValue, func, output, null, null); } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index d1e89a850..990ca2c7f 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -45,21 +45,22 @@ static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator } static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, Mask mask, @Nullable DBinaryOperator accum) { - checkSameShape(initialOutput, output, true); - - // TODO: operate on a bitset/boolean[] here -> also just one for-loop needed - for (int col = 0; col < output.getNumCols(); col++) { - for (int row = 0; row < output.numRows; row++) { - if (mask.isSet(row, col)) { - if (accum != null) { - // combine previous value and computed value - output.unsafe_set(row, col, accum.apply(output.get(row, col), initialOutput.get(row, col))); + if (initialOutput != null) { + checkSameShape(initialOutput, output, true); + + // TODO: operate on a bitset/boolean[] here -> also just one for-loop needed + for (int col = 0; col < output.getNumCols(); col++) { + for (int row = 0; row < output.numRows; row++) { + if (mask.isSet(row, col)) { + if (accum != null) { + // combine previous value and computed value + output.unsafe_set(row, col, accum.apply(output.get(row, col), initialOutput.get(row, col))); + } + // else output value just keeps as it is + } else { + // just use previous value as it shouldnt be computed in the first place + output.unsafe_set(row, col, initialOutput.get(row, col)); } - // else output value just keeps as it is - } - else { - // just use previous value as it shouldnt be computed in the first place - output.unsafe_set(row, col, initialOutput.get(row, col)); } } } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index c8b00cbd2..069e59621 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -18,6 +18,8 @@ package org.ejml.sparse.csc.mult; +import org.ejml.data.DMatrixD1; +import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparse; import org.ejml.data.DMatrixSparseCSC; import org.ejml.masks.DMasks; @@ -225,8 +227,45 @@ public void apply() { assertMaskedResult(prevResult, result, resultWithMask, mask); } - // TODO: test reduce as well as general test of MaskUtil + @Test + public void reduceRowWise() { + DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); + inputVector.set(0, 3, 0.5); + inputVector.set(0, 5, 0.6); + + DMatrixRMaj prevResult = new DMatrixRMaj(1,7); + prevResult.set(0, 5, 0.6); + prevResult.set(0, 2, 99); + + boolean negated = true; + Mask mask = DMasks.of(prevResult, negated); + + DBinaryOperator first = (x, y) -> x; + DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), null, null); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), mask, first); + + assertMaskedResult(prevResult, result, resultWithMask, mask); + } + + @Test + public void reduceColumnWise() { + DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); + inputVector.set(0, 3, 0.5); + inputVector.set(0, 5, 0.6); + + DMatrixRMaj prevResult = new DMatrixRMaj(1,7); + prevResult.set(0, 5, 0.6); + prevResult.set(0, 2, 99); + boolean negated = true; + Mask mask = DMasks.of(prevResult, negated); + + DBinaryOperator first = (x, y) -> x; + DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), null, null); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), mask, first); + + assertMaskedResult(prevResult, result, resultWithMask, mask); + } private void assertMaskedResult(DMatrixSparseCSC prevResult, DMatrixSparseCSC found, DMatrixSparseCSC foundWithMask, Mask mask) { @@ -249,6 +288,18 @@ private void assertMaskedResult(DMatrixSparseCSC prevResult, DMatrixSparseCSC fo }); } + private void assertMaskedResult(DMatrixD1 prevResult, DMatrixD1 found, DMatrixD1 foundWithMask, Mask mask) { + for (int row = 0; row < found.getNumRows(); row++) { + for (int col = 0; col < found.getNumCols(); col++) { + if (mask.isSet(row, col)) { + assertEquals(found.get(row, col), foundWithMask.get(row, col), "Field should have been computed"); + } else { + assertEquals(prevResult.get(row, col), foundWithMask.get(row, col), "Field from initial result was overwritten"); + } + } + } + } + private void assertMaskedResult(double[] prevResult, double[] found, double[] foundWithMask, PrimitiveDMask mask) { for (int i = 0; i < found.length; i++) { if (mask.isSet(i)) { From 4e489bd9988d0170d3002fb607ac95b505383900 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 10 Aug 2020 18:25:45 +0200 Subject: [PATCH 33/48] Parameterize masked tests --- .../csc/mult/TestMaskedOperators_DSCC.java | 286 +++++++----------- 1 file changed, 115 insertions(+), 171 deletions(-) diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 069e59621..44f9d9a4d 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -18,251 +18,195 @@ package org.ejml.sparse.csc.mult; -import org.ejml.data.DMatrixD1; -import org.ejml.data.DMatrixRMaj; -import org.ejml.data.DMatrixSparse; -import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.*; import org.ejml.masks.DMasks; import org.ejml.masks.Mask; import org.ejml.masks.PrimitiveDMask; +import org.ejml.ops.ConvertMatrixType; import org.ejml.ops.DBinaryOperator; import org.ejml.ops.DSemiRing; import org.ejml.ops.DSemiRings; import org.ejml.sparse.csc.CommonOpsWithSemiRing_DSCC; import org.ejml.sparse.csc.CommonOps_DSCC; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; import java.util.Iterator; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; public class TestMaskedOperators_DSCC extends BaseTestMatrixMatrixOpsWithSemiRing_DSCC { - DSemiRing semiRing = DSemiRings.PLUS_TIMES; + static DSemiRing semiRing = DSemiRings.PLUS_TIMES; - @Test - public void mult_v_A() { - - // graphblas == following outgoing edges of source nodes - double v[] = new double[7]; - Arrays.fill(v, semiRing.add.id); - v[3] = 0.5; - v[0] = 0.6; + private static Stream primitiveVectorSource() { + double[] inputVector = new double[7]; + Arrays.fill(inputVector, semiRing.add.id); + inputVector[3] = 0.5; + inputVector[0] = 0.6; double[] prevResult = new double[7]; prevResult[3] = 99; prevResult[0] = 42; - double[] found = prevResult.clone(); - double[] foundWithMask = prevResult.clone(); - // == dont calculate for existing entries (currently still zero-ing out very likely) - PrimitiveDMask mask = new PrimitiveDMask(prevResult, true); + return Stream.of( + Arguments.of(inputVector, prevResult, new PrimitiveDMask(prevResult, true)), + Arguments.of(inputVector, prevResult, new PrimitiveDMask(prevResult, false)) + ); + } + + private static Stream sparseVectorSource() { + DMatrixSparseCSC otherMatrix = new DMatrixSparseCSC(1, 7); + otherMatrix.set(0, 3, 0.5); + otherMatrix.set(0, 4, 0.6); - MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, found, semiRing); - MatrixVectorMultWithSemiRing_DSCC.mult(v, inputMatrix, foundWithMask, semiRing, mask); + DMatrixSparseCSC prevResult = otherMatrix.copy(); + prevResult.set(0, 2, 99); - assertMaskedResult(prevResult, found, foundWithMask, mask); + return Stream.of( + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, true)), + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, false)), + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, false)), + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, true)) + ); } - @Test - public void mult_A_v() { - // graphblas == following incoming edges of source nodes - double[] v = new double[7]; - Arrays.fill(v, semiRing.add.id); - v[3] = 0.5; - v[4] = 0.6; + private static Stream sparseMatrixSource() { + DMatrixSparseCSC otherMatrix = new DMatrixSparseCSC(7, 7); + otherMatrix.set(0, 3, 0.5); + otherMatrix.set(0, 5, 0.6); - double[] prevResult = new double[7]; - prevResult[3] = 99; - prevResult[0] = 42; + DMatrixSparseCSC prevResult = new DMatrixSparseCSC(7, 7); + prevResult.set(0,0, 99); + prevResult.set(0,3, 42); + + return Stream.of( + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, true)), + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, false)), + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, false)), + Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, true)) + ); + } + + @ParameterizedTest + @MethodSource("primitiveVectorSource") + public void mult_v_A(double[] inputVector, double[] prevResult, PrimitiveDMask mask) { double[] found = prevResult.clone(); double[] foundWithMask = prevResult.clone(); - // == dont calculate for existing entries (currently still zero-ing out very likely) - PrimitiveDMask mask = new PrimitiveDMask(prevResult, true); - - MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, found, semiRing); - MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, v, foundWithMask, semiRing, mask); + MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, inputMatrix, found, semiRing); + MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, inputMatrix, foundWithMask, semiRing, mask); assertMaskedResult(prevResult, found, foundWithMask, mask); } - // matrix, matrix ops - // TODO refactor common parts & parameterize masks - - @Test - public void mult_A_B() { - // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); - vector.set(0, 3, 0.5); - vector.set(0, 4, 0.6); - - DMatrixSparseCSC prevResult = vector.copy(); - prevResult.set(0, 2, 99); - - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, prevResult.copy(), semiRing, null); - - // TODO: parameterize test .. also changes expected result - boolean negated = true; - boolean structural = true; - Mask mask = DMasks.of(prevResult, negated, structural); + @ParameterizedTest + @MethodSource("primitiveVectorSource") + public void mult_A_v(double[] inputVector, double[] prevResult, PrimitiveDMask mask) { + double[] found = prevResult.clone(); + double[] foundWithMask = prevResult.clone(); - DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, prevResult.copy(), semiRing, mask); + MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, inputVector, found, semiRing); + MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, inputVector, foundWithMask, semiRing, mask); assertMaskedResult(prevResult, found, foundWithMask, mask); } - @Test - public void mult_T_A_B() { - // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC vector = new DMatrixSparseCSC(1, 7); - vector.set(0, 3, 0.5); - vector.set(0, 5, 0.6); - - DMatrixSparseCSC prevResult = vector.copy(); - prevResult.set(0, 2, 99); + // matrix, matrix ops + @ParameterizedTest + @MethodSource("sparseVectorSource") + public void mult_A_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResult, Mask mask) { + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(sparseVector, inputMatrix, prevResult.copy(), semiRing, null); + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(sparseVector, inputMatrix, prevResult.copy(), semiRing, mask); - DMatrixSparseCSC transposed_vector = CommonOps_DSCC.transpose(vector, null, null); + assertMaskedResult(prevResult, found, foundWithMask, mask); + } - DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, prevResult.copy(), semiRing, null, null, null); + // Todo: solve problem with non-negated masks - boolean negated = true; - boolean structural = true; - Mask mask = DMasks.of(prevResult, negated, structural); + @ParameterizedTest + @MethodSource("sparseVectorSource") + public void mult_T_A_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResult, Mask mask) { + DMatrixSparseCSC transposed_vector = CommonOps_DSCC.transpose(sparseVector, null, null); - DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA(transposed_vector, inputMatrix, prevResult.copy(), semiRing, mask, null, null); + DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA( + transposed_vector, inputMatrix, prevResult.copy(), semiRing, null, null, null); + DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA( + transposed_vector, inputMatrix, prevResult.copy(), semiRing, mask, null, null); assertMaskedResult(prevResult, foundTA, foundTAWithMask, mask); } - @Test - public void mult_A_T_B() { - // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); - inputVector.set(0, 3, 0.5); - inputVector.set(0, 5, 0.6); - + @ParameterizedTest + @MethodSource("sparseVectorSource") + public void mult_A_T_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResult, Mask mask) { DMatrixSparseCSC transposed_matrix = CommonOps_DSCC.transpose(inputMatrix, null, null); - DMatrixSparseCSC prevResult = inputVector.copy(); - prevResult.set(0, 2, 99); - - DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB(inputVector, transposed_matrix, prevResult.copy(), semiRing, null, null, null); - - boolean negated = true; - boolean structural = true; - Mask mask = DMasks.of(prevResult, negated, structural); - - DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB(inputVector, transposed_matrix, prevResult.copy(), semiRing, mask, null, null); + DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB( + sparseVector, transposed_matrix, prevResult.copy(), semiRing, null, null, null); + DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB( + sparseVector, transposed_matrix, prevResult.copy(), semiRing, mask, null, null); assertMaskedResult(prevResult, foundTB, foundTBWithMask, mask); } - @Test - public void add_A_B() { - // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC otherMatrix = new DMatrixSparseCSC(7, 7); - otherMatrix.set(0, 3, 0.5); - otherMatrix.set(0, 5, 0.6); - - DMatrixSparseCSC resultInput = new DMatrixSparseCSC(7, 7); - // these should not be kept in the result (as negated mask) - resultInput.set(0,0, 99); - resultInput.set(0,3, 42); - - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, otherMatrix, 1, inputMatrix, resultInput.copy(), semiRing, null, null, null); - - boolean negated = true; - boolean structural = true; - Mask mask = DMasks.of(resultInput, negated, structural); - - DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.add(1, otherMatrix, 1, inputMatrix, resultInput.copy(), semiRing, mask, null, null); + @ParameterizedTest + @MethodSource("sparseMatrixSource") + public void add_A_B(DMatrixSparseCSC otherMatrix, DMatrixSparseCSC prevResult, Mask mask) { + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add( + 1, otherMatrix, 1, inputMatrix, prevResult.copy(), semiRing, null, null, null); + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.add( + 1, otherMatrix, 1, inputMatrix, prevResult.copy(), semiRing, mask, null, null); - assertMaskedResult(resultInput, found, foundWithMask, mask); + assertMaskedResult(prevResult, found, foundWithMask, mask); } - @Test - public void elementWiseMult() { - // graphblas == following outgoing edges of source nodes - DMatrixSparseCSC otherMatrix = new DMatrixSparseCSC(7, 7); - otherMatrix.set(0, 3, 0.5); - otherMatrix.set(0, 5, 0.6); + @ParameterizedTest + @MethodSource("sparseMatrixSource") + public void elementWiseMult(DMatrixSparseCSC otherMatrix, DMatrixSparseCSC prevResult, Mask mask) { + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult( + otherMatrix, inputMatrix, prevResult.copy(), semiRing, null, null, null); + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.elementMult( + otherMatrix, inputMatrix, prevResult.copy(), semiRing, mask, null, null); - DMatrixSparseCSC resultInput = new DMatrixSparseCSC(7, 7); - // these should not be kept in the result (as negated mask) - resultInput.set(0,0, 99); - resultInput.set(0,3, 42); - - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(otherMatrix, inputMatrix, resultInput.copy(), semiRing, null, null, null); - - boolean negated = true; - boolean structural = true; - Mask mask = DMasks.of(resultInput, negated, structural); - - DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.elementMult(otherMatrix, inputMatrix, resultInput.copy(), semiRing, mask, null, null); - - assertMaskedResult(resultInput, found, foundWithMask, mask); + assertMaskedResult(prevResult, found, foundWithMask, mask); } - @Test - public void apply() { - DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); - inputVector.set(0, 3, 0.5); - inputVector.set(0, 5, 0.6); - - DMatrixSparseCSC prevResult = inputVector.copy(); - prevResult.set(0, 2, 99); - - boolean negated = true; - boolean structural = true; - Mask mask = DMasks.of(prevResult, negated, structural); - + @ParameterizedTest + @MethodSource("sparseVectorSource") + public void apply(DMatrixSparseCSC vector, DMatrixSparseCSC prevResult, Mask mask) { DBinaryOperator first = (x, y) -> x; - DMatrixSparseCSC result = CommonOps_DSCC.apply(inputVector, a -> a * 2, prevResult.copy(), null, null); - DMatrixSparseCSC resultWithMask = CommonOps_DSCC.apply(inputVector, a -> a * 2, prevResult.copy(), mask, first); + DMatrixSparseCSC result = CommonOps_DSCC.apply(vector, a -> a * 2, prevResult.copy(), null, null); + DMatrixSparseCSC resultWithMask = CommonOps_DSCC.apply(vector, a -> a * 2, prevResult.copy(), mask, first); assertMaskedResult(prevResult, result, resultWithMask, mask); } - @Test - public void reduceRowWise() { - DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); - inputVector.set(0, 3, 0.5); - inputVector.set(0, 5, 0.6); - - DMatrixRMaj prevResult = new DMatrixRMaj(1,7); - prevResult.set(0, 5, 0.6); - prevResult.set(0, 2, 99); - - boolean negated = true; - Mask mask = DMasks.of(prevResult, negated); + @ParameterizedTest + @MethodSource("sparseVectorSource") + public void reduceRowWise(DMatrixSparseCSC vector, DMatrixSparseCSC prevSparseResult, Mask mask) { + DMatrixRMaj prevResult = (DMatrixRMaj) ConvertMatrixType.convert(prevSparseResult, MatrixType.DDRM); DBinaryOperator first = (x, y) -> x; - DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), null, null); - DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), mask, first); + DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(vector, 0, Double::sum, prevResult.copy(), null, null); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(vector, 0, Double::sum, prevResult.copy(), mask, first); assertMaskedResult(prevResult, result, resultWithMask, mask); } - @Test - public void reduceColumnWise() { - DMatrixSparseCSC inputVector = new DMatrixSparseCSC(1, 7); - inputVector.set(0, 3, 0.5); - inputVector.set(0, 5, 0.6); - - DMatrixRMaj prevResult = new DMatrixRMaj(1,7); - prevResult.set(0, 5, 0.6); - prevResult.set(0, 2, 99); - - boolean negated = true; - Mask mask = DMasks.of(prevResult, negated); + @ParameterizedTest + @MethodSource("sparseVectorSource") + public void reduceColumnWise(DMatrixSparseCSC vector, DMatrixSparseCSC prevSparseResult, Mask mask) { + DMatrixRMaj prevResult = (DMatrixRMaj) ConvertMatrixType.convert(prevSparseResult, MatrixType.DDRM); DBinaryOperator first = (x, y) -> x; - DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), null, null); - DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(inputVector, 0, (a,b) -> a + b, prevResult.copy(), mask, first); + DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(vector, 0, Double::sum, prevResult.copy(), null, null); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(vector, 0, Double::sum, prevResult.copy(), mask, first); assertMaskedResult(prevResult, result, resultWithMask, mask); } From fa52eb43b0ff152e25b829cd008c6d5285f8acb0 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 10 Aug 2020 19:44:23 +0200 Subject: [PATCH 34/48] Check if mask is compatible with result .. and correct default accumulator --- main/ejml-core/src/org/ejml/UtilEjml.java | 7 ++-- main/ejml-core/src/org/ejml/masks/Mask.java | 32 +++++++++++++++++++ .../src/org/ejml/masks/PrimitiveDMask.java | 10 ++++++ .../src/org/ejml/masks/SparseDMask.java | 10 ++++++ .../org/ejml/masks/SparseStructuralMask.java | 10 ++++++ .../csc/CommonOpsWithSemiRing_DSCC.java | 31 ++++++++++++++---- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 10 +++++- 7 files changed, 99 insertions(+), 11 deletions(-) diff --git a/main/ejml-core/src/org/ejml/UtilEjml.java b/main/ejml-core/src/org/ejml/UtilEjml.java index ec735e1ce..317a6eb2e 100644 --- a/main/ejml-core/src/org/ejml/UtilEjml.java +++ b/main/ejml-core/src/org/ejml/UtilEjml.java @@ -552,10 +552,11 @@ public static boolean hasNullableArgument(Method func) { } /** - * Check if output matrix needs to be cached for later merge with actual result + * Check if initialOutput matrix needs to be cached for later merge with actual result * (in order to prevent deleting entries which shouldnt be overwritten, e.g. !mask.isSet()) */ - public static boolean useInitialOutput(Mask mask, Matrix output, int numRows, int numCols) { - return mask != null && output != null && output.getNumRows() == numRows && output.getNumCols() == numCols; + public static boolean useInitialOutput(Mask mask, Matrix initialOutput, int numRows, int numCols) { + // check for dimensions as an indicator (TODO check if actually this is not misleading) + return mask != null && initialOutput != null && initialOutput.getNumRows() == numRows && initialOutput.getNumCols() == numCols; } } diff --git a/main/ejml-core/src/org/ejml/masks/Mask.java b/main/ejml-core/src/org/ejml/masks/Mask.java index 476cbef14..9fe4dbe5c 100644 --- a/main/ejml-core/src/org/ejml/masks/Mask.java +++ b/main/ejml-core/src/org/ejml/masks/Mask.java @@ -19,6 +19,9 @@ package org.ejml.masks; +import org.ejml.MatrixDimensionException; +import org.ejml.data.Matrix; + /** * Mask used for specifying which matrix entries should be computed */ @@ -32,6 +35,35 @@ public Mask(boolean negated) { public abstract boolean isSet(int row, int col); + public abstract int getNumCols(); + + public abstract int getNumRows(); + + public void print() { + String result = ""; + for (int row = 0; row < getNumRows(); row++) { + for (int col = 0; col < getNumCols(); col++) { + result += isSet(row, col) ? "+ " : "- "; + } + result += System.lineSeparator(); + } + + System.out.println(result); + }; + + /** + * Checks whether the dimensions of the mask and matrix match + * @param matrix the mask is applied to + */ + public void compatible(Matrix matrix) { + if (matrix.getNumCols() != getNumCols() || matrix.getNumRows() != getNumRows()) { + throw new MatrixDimensionException(String.format( + "Mask of (%d, %d) cannot be applied for matrix (%d, %d)", + getNumRows(), getNumCols(), matrix.getNumCols(), matrix.getNumCols() + )); + } + } + // TODO: use an Iterator as it should be faster as stepping can be used -> no need to call for each entry? // Problem .. dense matrices are row-based, whereas existing sparse matrix format is column based //public abstract Iterator diff --git a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java index c946fd1cd..d848430e9 100644 --- a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java +++ b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java @@ -52,6 +52,16 @@ public boolean isSet(int row, int col) { return negated ^ (values[row * numCols + col] != zeroElement); } + @Override + public int getNumCols() { + return numCols; + } + + @Override + public int getNumRows() { + return values.length / numCols; + } + public boolean isSet(int index) { return negated ^ (values[index] != zeroElement); } diff --git a/main/ejml-core/src/org/ejml/masks/SparseDMask.java b/main/ejml-core/src/org/ejml/masks/SparseDMask.java index 702ec0174..7c8a97c0e 100644 --- a/main/ejml-core/src/org/ejml/masks/SparseDMask.java +++ b/main/ejml-core/src/org/ejml/masks/SparseDMask.java @@ -38,4 +38,14 @@ public SparseDMask(DMatrixSparseCSC matrix, boolean negated, double zeroElement) public boolean isSet(int row, int col) { return negated ^ (matrix.unsafe_get(row, col) != zeroElement); } + + @Override + public int getNumCols() { + return matrix.numCols; + } + + @Override + public int getNumRows() { + return matrix.numRows; + } } diff --git a/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java b/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java index 84dbea0e2..05beccce7 100644 --- a/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java +++ b/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java @@ -36,4 +36,14 @@ public SparseStructuralMask(MatrixSparse matrix, boolean negated) { public boolean isSet(int row, int col) { return negated ^ matrix.isAssigned(row, col); } + + @Override + public int getNumCols() { + return matrix.getNumCols(); + } + + @Override + public int getNumRows() { + return matrix.getNumRows(); + } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index da06f60ca..18b4c0499 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -25,7 +25,7 @@ import org.ejml.data.DMatrixSparseCSC; import org.ejml.data.IGrowArray; import org.ejml.masks.Mask; -import org.ejml.ops.DMonoid; +import org.ejml.ops.DBinaryOperator; import org.ejml.ops.DSemiRing; import org.ejml.sparse.csc.misc.ImplCommonOpsWithSemiRing_DSCC; import org.ejml.sparse.csc.mult.ImplSparseSparseMultWithSemiRing_DSCC; @@ -39,6 +39,8 @@ public class CommonOpsWithSemiRing_DSCC { + private static final DBinaryOperator SECOND = (x, y) -> y; + public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, @Nullable Mask mask) { return mult(A, B, output, semiRing, mask, null, null); @@ -62,11 +64,14 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul // !! important to do before reshape DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numCols) ? output.copy() : null; - output = reshapeOrDeclare(output,A,A.numRows,B.numCols); + if (mask != null) { + mask.compatible(output); + } + ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add.func, initialOutput); + return combineOutputs(output, SECOND, initialOutput); } public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, @@ -76,10 +81,13 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B // !! important to do before reshape DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numCols, B.numCols) ? output.copy() : null; output = reshapeOrDeclare(output,A,A.numCols,B.numCols); + if (mask != null) { + mask.compatible(output); + } ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add.func, initialOutput); + return combineOutputs(output, SECOND, initialOutput); } /** @@ -99,9 +107,12 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numRows) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, B.numRows); + if (mask != null) { + mask.compatible(output); + } if (!B.isIndicesSorted()) B.sortIndices(null); ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add.func, initialOutput); + return combineOutputs(output, SECOND, initialOutput); } @@ -241,10 +252,13 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, A.numCols); + if (mask != null) { + mask.compatible(output); + } ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add.func, initialOutput); + return combineOutputs(output, SECOND, initialOutput); } /** @@ -264,10 +278,13 @@ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, A.numCols); + if (mask != null) { + mask.compatible(output); + } ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, semiRing.add.func, initialOutput); + return combineOutputs(output, SECOND, initialOutput); } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index 19e786a3d..c0003736a 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -38,7 +38,6 @@ import javax.annotation.Nullable; import java.util.Arrays; -import java.util.BitSet; import static org.ejml.UtilEjml.*; import static org.ejml.sparse.csc.MaskUtil_DSCC.combineOutputs; @@ -1901,6 +1900,9 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func @Nullable DMatrixSparseCSC output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, input.numCols) ? output.copy() : null; output = reshapeOrDeclare(output, input); + if (mask != null) { + mask.compatible(output); + } // dont check for the mask here as the computation is too simple to get any improvements when skipping // would more likely prohibit vectorisation @@ -1958,6 +1960,9 @@ public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initVa @Nullable DMatrixRMaj output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { DMatrixRMaj initialOutput = UtilEjml.useInitialOutput(mask, output, 1, input.numCols) ? output.copy() : null; output = reshapeOrDeclare(output, 1, input.numCols); + if (mask != null) { + mask.compatible(output); + } for (int col = 0; col < input.numCols; col++) { int start = input.col_idx[col]; @@ -1999,6 +2004,9 @@ public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue @Nullable DMatrixRMaj output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { DMatrixRMaj initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, 1) ? output.copy() : null; output = reshapeOrDeclare(output, input.numRows, 1); + if (mask != null) { + mask.compatible(output); + } Arrays.fill(output.data, initValue); From ecd8adc73c56af4024e6f07800defc797b3c4c6d Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Mon, 10 Aug 2020 19:45:22 +0200 Subject: [PATCH 35/48] Fix reduce tests by using meaningful input .. before dimensions did not even match --- .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 1 + .../csc/mult/TestMaskedOperators_DSCC.java | 48 ++++++++++++++----- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index 990ca2c7f..bfcac2702 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -32,6 +32,7 @@ public class MaskUtil_DSCC { // for applying mask and accumulator (output gets overwritten) + // ! assumes that the mask is already applied to the output .. e.g. unset fields not even computed static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { if (initialOutput != null) { // memory overhead .. maybe also can reuse something? diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 44f9d9a4d..8f467874a 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -22,7 +22,6 @@ import org.ejml.masks.DMasks; import org.ejml.masks.Mask; import org.ejml.masks.PrimitiveDMask; -import org.ejml.ops.ConvertMatrixType; import org.ejml.ops.DBinaryOperator; import org.ejml.ops.DSemiRing; import org.ejml.ops.DSemiRings; @@ -31,6 +30,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import java.util.Arrays; import java.util.Iterator; @@ -180,33 +180,55 @@ public void elementWiseMult(DMatrixSparseCSC otherMatrix, DMatrixSparseCSC prevR @ParameterizedTest @MethodSource("sparseVectorSource") public void apply(DMatrixSparseCSC vector, DMatrixSparseCSC prevResult, Mask mask) { - DBinaryOperator first = (x, y) -> x; + DBinaryOperator second = (x, y) -> y; DMatrixSparseCSC result = CommonOps_DSCC.apply(vector, a -> a * 2, prevResult.copy(), null, null); - DMatrixSparseCSC resultWithMask = CommonOps_DSCC.apply(vector, a -> a * 2, prevResult.copy(), mask, first); + DMatrixSparseCSC resultWithMask = CommonOps_DSCC.apply(vector, a -> a * 2, prevResult.copy(), mask, second); assertMaskedResult(prevResult, result, resultWithMask, mask); } @ParameterizedTest - @MethodSource("sparseVectorSource") - public void reduceRowWise(DMatrixSparseCSC vector, DMatrixSparseCSC prevSparseResult, Mask mask) { - DMatrixRMaj prevResult = (DMatrixRMaj) ConvertMatrixType.convert(prevSparseResult, MatrixType.DDRM); + @ValueSource(strings = {"true", "false"}) + public void reduceRowWise(boolean negatedMask) { + DMatrixSparseCSC matrix = new DMatrixSparseCSC(7, 7); + matrix.set(0, 3, 0.5); + matrix.set(0, 4, 0.6); + matrix.set(4, 0, 0.1); + matrix.set(3, 0, 0.2); + + double[] prevPrimitiveResult = new double[7]; + prevPrimitiveResult[2] = 42; + prevPrimitiveResult[0] = 99; + + DMatrixRMaj prevResult = DMatrixRMaj.wrap(matrix.numRows, 1, prevPrimitiveResult); + Mask mask = DMasks.of(prevResult, negatedMask); DBinaryOperator first = (x, y) -> x; - DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(vector, 0, Double::sum, prevResult.copy(), null, null); - DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(vector, 0, Double::sum, prevResult.copy(), mask, first); + DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), null, null); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), mask, first); assertMaskedResult(prevResult, result, resultWithMask, mask); } @ParameterizedTest - @MethodSource("sparseVectorSource") - public void reduceColumnWise(DMatrixSparseCSC vector, DMatrixSparseCSC prevSparseResult, Mask mask) { - DMatrixRMaj prevResult = (DMatrixRMaj) ConvertMatrixType.convert(prevSparseResult, MatrixType.DDRM); + @ValueSource(strings = {"true", "false"}) + public void reduceColumnWise(boolean negatedMask) { + DMatrixSparseCSC matrix = new DMatrixSparseCSC(7, 7); + matrix.set(0, 3, 0.5); + matrix.set(0, 4, 0.6); + matrix.set(4, 0, 0.1); + matrix.set(3, 0, 0.2); + + double[] prevPrimitiveResult = new double[7]; + prevPrimitiveResult[2] = 42; + prevPrimitiveResult[0] = 99; + + DMatrixRMaj prevResult = DMatrixRMaj.wrap(1, matrix.numCols, prevPrimitiveResult); + Mask mask = DMasks.of(prevResult, negatedMask); DBinaryOperator first = (x, y) -> x; - DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(vector, 0, Double::sum, prevResult.copy(), null, null); - DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(vector, 0, Double::sum, prevResult.copy(), mask, first); + DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), null, null); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), mask, first); assertMaskedResult(prevResult, result, resultWithMask, mask); } From 11be867515c9c0ab4010e3187a3249036c6e8705 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 11 Aug 2020 16:16:29 +0200 Subject: [PATCH 36/48] Fix incorrect result structure based on mask only applied to values (entry was still added in nz_rows) basically moved the mask check towards `multAddColA` --- .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 2 +- .../misc/ImplCommonOpsWithSemiRing_DSCC.java | 9 ++--- ...ImplSparseSparseMultWithSemiRing_DSCC.java | 39 +++++++++---------- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index bfcac2702..92ef178c1 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -32,7 +32,7 @@ public class MaskUtil_DSCC { // for applying mask and accumulator (output gets overwritten) - // ! assumes that the mask is already applied to the output .. e.g. unset fields not even computed + // ! assumes that the mask is already applied to the output .. e.g. unset fields not even computed (and not assigned) static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { if (initialOutput != null) { // memory overhead .. maybe also can reuse something? diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index 0a5a4b06c..14582f936 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -57,18 +57,15 @@ public static void add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSpa for (int col = 0; col < A.numCols; col++) { C.col_idx[col] = C.nz_length; - multAddColA(A, col, alpha, C, col + 1, semiRing, x, w); - multAddColA(B, col, beta, C, col + 1, semiRing, x, w); + multAddColA(A, col, alpha, C, col + 1, semiRing, mask, x, w); + multAddColA(B, col, beta, C, col + 1, semiRing, mask, x, w); // take the values in the dense vector 'x' and put them into 'C' int idxC0 = C.col_idx[col]; int idxC1 = C.col_idx[col + 1]; for (int i = idxC0; i < idxC1; i++) { - // very likely not vectorized now ... - if(mask == null || mask.isSet(C.nz_rows[i], col)) { - C.nz_values[i] = x[C.nz_rows[i]]; - } + C.nz_values[i] = x[C.nz_rows[i]]; } } C.col_idx[A.numCols] = C.nz_length; diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 7b22ceced..332a96a1a 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -70,7 +70,7 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC int rowB = B.nz_rows[bi]; double valB = B.nz_values[bi]; // B(k,j) k=rowB j=colB - multAddColA(A, rowB, valB, C, colB + 1, semiRing, x, w); + multAddColA(A, rowB, valB, C, colB + 1, semiRing, mask, x, w); } // take the values in the dense vector 'x' and put them into 'C' @@ -78,10 +78,7 @@ public static void mult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC int idxC1 = C.col_idx[colB + 1]; for (int i = idxC0; i < idxC1; i++) { - // this will destroy simdi usage here .. (hopefully not if mask == null) - if (mask == null || mask.isSet(C.nz_rows[i], bj - 1)) { - C.nz_values[i] = x[C.nz_rows[i]]; - } + C.nz_values[i] = x[C.nz_rows[i]]; } idx0 = idx1; @@ -193,7 +190,7 @@ else if (!CommonOps_DSCC.checkIndicesSorted(B)) { if (bi < B.col_idx[colB + 1]) { int row = B.nz_rows[bi]; if (row == colC) { - multAddColA(A, colB, B.nz_values[bi], C, mark, semiRing, x, w); + multAddColA(A, colB, B.nz_values[bi], C, mark, semiRing, mask, x, w); w[locationB + colB]++; } } @@ -204,10 +201,7 @@ else if (!CommonOps_DSCC.checkIndicesSorted(B)) { int idxC1 = C.col_idx[colC + 1]; for (int i = idxC0; i < idxC1; i++) { - // this will destroy simdi usage here .. (hopefully not if mask == null) - if(mask == null || mask.isSet(C.nz_rows[i], colC)) { - C.nz_values[i] = x[C.nz_rows[i]]; - } + C.nz_values[i] = x[C.nz_rows[i]]; } } } @@ -220,7 +214,7 @@ else if (!CommonOps_DSCC.checkIndicesSorted(B)) { public static void multAddColA(DMatrixSparseCSC A, int colA, double alpha, DMatrixSparseCSC C, int mark, - DSemiRing semiRing, + DSemiRing semiRing, @Nullable Mask mask, double x[], int w[]) { int idxA0 = A.col_idx[colA]; int idxA1 = A.col_idx[colA + 1]; @@ -228,17 +222,20 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, for (int j = idxA0; j < idxA1; j++) { int row = A.nz_rows[j]; - if (w[row] < mark) { - if (C.nz_length >= C.nz_rows.length) { - C.growMaxLength(C.nz_length * 2 + 1, true); - } + // TODO: only use iterator over a single column mask values here + if (mask == null || mask.isSet(row, mark)) { + if (w[row] < mark) { + if (C.nz_length >= C.nz_rows.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } - w[row] = mark; - C.nz_rows[C.nz_length] = row; - C.col_idx[mark] = ++C.nz_length; - x[row] = semiRing.mult.func.apply(A.nz_values[j], alpha); - } else { - x[row] = semiRing.add.func.apply(x[row], semiRing.mult.func.apply(A.nz_values[j], alpha)); + w[row] = mark; + C.nz_rows[C.nz_length] = row; + C.col_idx[mark] = ++C.nz_length; + x[row] = semiRing.mult.func.apply(A.nz_values[j], alpha); + } else { + x[row] = semiRing.add.func.apply(x[row], semiRing.mult.func.apply(A.nz_values[j], alpha)); + } } } } From 9f5bb99b6f76be2b2e310ad206afcf062655f5e8 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 11 Aug 2020 16:58:04 +0200 Subject: [PATCH 37/48] Fix result for apply with mask .. basically need the mask to correctly combine the results, as unneeded entries may still exists in the output (different to other ops) --- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 10 ++++- .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 41 +++++++++++-------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index c0003736a..913221316 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -1899,18 +1899,24 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func, @Nullable DMatrixSparseCSC output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, input.numCols) ? output.copy() : null; - output = reshapeOrDeclare(output, input); + // set correct structure + if (output == null) { + output = input.createLike(); + } else if (input != output) { + output.copyStructure(input); + } if (mask != null) { mask.compatible(output); } + // dont check for the mask here as the computation is too simple to get any improvements when skipping // would more likely prohibit vectorisation for (int i = 0; i < input.nz_length; i++) { output.nz_values[i] = func.apply(input.nz_values[i]); } - return combineOutputs(output, accum, initialOutput); + return combineOutputs(output, mask, accum, initialOutput); } /** diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index 92ef178c1..172544b6e 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -33,18 +33,23 @@ public class MaskUtil_DSCC { // for applying mask and accumulator (output gets overwritten) // ! assumes that the mask is already applied to the output .. e.g. unset fields not even computed (and not assigned) - static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { + // ! the mask is only needed for `apply` there the mask is not applied to the result structure before + static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, @Nullable Mask mask, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { if (initialOutput != null) { // memory overhead .. maybe also can reuse something? DMatrixSparseCSC combinedOutput = output.createLike(); // instead of "semiRing.add" this could be a dedicated accumulator - add(initialOutput, output, combinedOutput, accum, null, null); + add(initialOutput, output, combinedOutput, mask, accum, null, null); // is the previous result of C gc-able? (should be) output = combinedOutput; } return output; } + static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { + return combineOutputs(output, null, accum, initialOutput); + } + static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, Mask mask, @Nullable DBinaryOperator accum) { if (initialOutput != null) { checkSameShape(initialOutput, output, true); @@ -80,7 +85,7 @@ static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, * @param gw (Optional) Storage for internal workspace. Can be null. * @param gx (Optional) Storage for internal workspace. Can be null. */ - public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, DBinaryOperator accum, + public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC C, @Nullable Mask mask, DBinaryOperator accum, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { double[] x = adjust(gx, A.numRows); int[] w = adjust(gw, A.numRows, A.numRows); @@ -93,9 +98,9 @@ public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC // always take the values of A // second as x[row] would be the first argument - multAddColA(A, col, C, col + 1, (a, b) -> b, x, w); - // accum values of A and B - multAddColA(B, col, C, col + 1, accum, x, w); + multAddColA(A, col, C, col + 1, null, (a, b) -> b, x, w); + // accum values of A and B (if entry is set in mask) + multAddColA(B, col, C, col + 1, mask, accum, x, w); // take the values in the dense vector 'x' and put them into 'C' int idxC0 = C.col_idx[col]; @@ -114,6 +119,7 @@ public static void add(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC */ public static void multAddColA(DMatrixSparseCSC A, int colA, DMatrixSparseCSC C, int mark, + @Nullable Mask mask, DBinaryOperator accum, double x[], int w[]) { int idxA0 = A.col_idx[colA]; @@ -121,19 +127,20 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, for (int j = idxA0; j < idxA1; j++) { int row = A.nz_rows[j]; + if (mask == null || mask.isSet(row, colA)) { + if (w[row] < mark) { + if (C.nz_length >= C.nz_rows.length) { + C.growMaxLength(C.nz_length * 2 + 1, true); + } - if (w[row] < mark) { - if (C.nz_length >= C.nz_rows.length) { - C.growMaxLength(C.nz_length * 2 + 1, true); + w[row] = mark; + C.nz_rows[C.nz_length] = row; + C.col_idx[mark] = ++C.nz_length; + x[row] = A.nz_values[j]; + } else if (accum != null) { + // if it is null .. x[row] can just stay the same + x[row] = accum.apply(x[row], A.nz_values[j]); } - - w[row] = mark; - C.nz_rows[C.nz_length] = row; - C.col_idx[mark] = ++C.nz_length; - x[row] = A.nz_values[j]; - } else if (accum != null) { - // if it is null .. x[row] can just stay the same - x[row] = accum.apply(x[row], A.nz_values[j]); } } } From 3cbce8da1b0fa2eda902862bc17eff8e5bdc135e Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 11 Aug 2020 19:11:27 +0200 Subject: [PATCH 38/48] Fix a bug in `multAddColA` based on the incorrect target column .. off by one error again --- main/ejml-core/src/org/ejml/UtilEjml.java | 2 +- .../ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java | 2 +- .../sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java | 6 ++++-- .../csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main/ejml-core/src/org/ejml/UtilEjml.java b/main/ejml-core/src/org/ejml/UtilEjml.java index 317a6eb2e..410f1ba98 100644 --- a/main/ejml-core/src/org/ejml/UtilEjml.java +++ b/main/ejml-core/src/org/ejml/UtilEjml.java @@ -556,7 +556,7 @@ public static boolean hasNullableArgument(Method func) { * (in order to prevent deleting entries which shouldnt be overwritten, e.g. !mask.isSet()) */ public static boolean useInitialOutput(Mask mask, Matrix initialOutput, int numRows, int numCols) { - // check for dimensions as an indicator (TODO check if actually this is not misleading) + // check for dimensions as an indicator (TODO check if actually this is not misleading .. replace by "replace" flag) return mask != null && initialOutput != null && initialOutput.getNumRows() == numRows && initialOutput.getNumCols() == numCols; } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index 172544b6e..3abc34c71 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -127,7 +127,7 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, for (int j = idxA0; j < idxA1; j++) { int row = A.nz_rows[j]; - if (mask == null || mask.isSet(row, colA)) { + if (mask == null || mask.isSet(row, mark - 1)) { if (w[row] < mark) { if (C.nz_length >= C.nz_rows.length) { C.growMaxLength(C.nz_length * 2 + 1, true); diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java index 14582f936..cc55c15c8 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/misc/ImplCommonOpsWithSemiRing_DSCC.java @@ -37,9 +37,11 @@ public class ImplCommonOpsWithSemiRing_DSCC { /** * Performs matrix addition:
- * C = A + B + * C = αA + βB * - * @param A Matrix + * @param alpha scalar value multiplied against A + * @param A Matrix + * @param beta scalar value multiplied against B * @param B Matrix * @param C Output matrix. * @param mask Mask for specifying which entries should be overwritten diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java index 332a96a1a..538eaf35e 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/ImplSparseSparseMultWithSemiRing_DSCC.java @@ -221,9 +221,9 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, for (int j = idxA0; j < idxA1; j++) { int row = A.nz_rows[j]; - // TODO: only use iterator over a single column mask values here - if (mask == null || mask.isSet(row, mark)) { + // mark - 1 is the actual target column + if (mask == null || mask.isSet(row, mark - 1)) { if (w[row] < mark) { if (C.nz_length >= C.nz_rows.length) { C.growMaxLength(C.nz_length * 2 + 1, true); From 608b72084d787f1f220c261562f94c5064e9efe3 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 11 Aug 2020 22:22:07 +0200 Subject: [PATCH 39/48] Parameterize accumulator for combining old matrix and new result --- .../csc/CommonOpsWithSemiRing_DSCC.java | 29 ++++++++++--------- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 2 +- .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 11 +++++-- .../csc/mult/TestMaskedOperators_DSCC.java | 26 ++++++++--------- ...TestMatrixMatrixMultWithSemiRing_DSCC.java | 10 +++---- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 18b4c0499..067a570de 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -39,11 +39,9 @@ public class CommonOpsWithSemiRing_DSCC { - private static final DBinaryOperator SECOND = (x, y) -> y; - public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask) { - return mult(A, B, output, semiRing, mask, null, null); + @Nullable Mask mask, @Nullable DBinaryOperator accumulator) { + return mult(A, B, output, semiRing, mask, accumulator, null, null); } /** @@ -57,7 +55,8 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, + @Nullable DGrowArray gx) { if (A.numCols != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); @@ -71,11 +70,12 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, SECOND, initialOutput); + return combineOutputs(output, accumulator, initialOutput); } public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); // !! important to do before reshape @@ -87,7 +87,7 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B ImplSparseSparseMultWithSemiRing_DSCC.multTransA(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, SECOND, initialOutput); + return combineOutputs(output, accumulator, initialOutput); } /** @@ -102,7 +102,8 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, + @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numRows) ? output.copy() : null; @@ -112,7 +113,7 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B } if (!B.isIndicesSorted()) B.sortIndices(null); ImplSparseSparseMultWithSemiRing_DSCC.multTransB(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, SECOND, initialOutput); + return combineOutputs(output, accumulator, initialOutput); } @@ -247,7 +248,7 @@ public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows || A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; @@ -258,7 +259,7 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta ImplCommonOpsWithSemiRing_DSCC.add(alpha, A, beta, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, SECOND, initialOutput); + return combineOutputs(output, accumulator, initialOutput); } /** @@ -273,7 +274,7 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; @@ -284,7 +285,7 @@ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC ImplCommonOpsWithSemiRing_DSCC.elementMult(A, B, output, semiRing, mask, gw, gx); - return combineOutputs(output, SECOND, initialOutput); + return combineOutputs(output, accumulator, initialOutput); } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index 913221316..a78fad900 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -1916,7 +1916,7 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func output.nz_values[i] = func.apply(input.nz_values[i]); } - return combineOutputs(output, mask, accum, initialOutput); + return combineOutputs(output, initialOutput, mask, accum); } /** diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index 3abc34c71..8b6382c43 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -31,11 +31,18 @@ import static org.ejml.UtilEjml.checkSameShape; public class MaskUtil_DSCC { + private static final DBinaryOperator SECOND = (x, y) -> y; + // for applying mask and accumulator (output gets overwritten) // ! assumes that the mask is already applied to the output .. e.g. unset fields not even computed (and not assigned) // ! the mask is only needed for `apply` there the mask is not applied to the result structure before - static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, @Nullable Mask mask, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { + static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DMatrixSparseCSC initialOutput, @Nullable Mask mask, DBinaryOperator accum) { if (initialOutput != null) { + if(accum == null) { + // e.g. just take the newly computed value + accum = SECOND; + } + // memory overhead .. maybe also can reuse something? DMatrixSparseCSC combinedOutput = output.createLike(); // instead of "semiRing.add" this could be a dedicated accumulator @@ -47,7 +54,7 @@ static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, @Nullable Mask m } static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DBinaryOperator accum, DMatrixSparseCSC initialOutput) { - return combineOutputs(output, null, accum, initialOutput); + return combineOutputs(output, initialOutput, null, accum); } static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, Mask mask, @Nullable DBinaryOperator accum) { diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 8f467874a..a8df7c3b5 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -120,8 +120,8 @@ public void mult_A_v(double[] inputVector, double[] prevResult, PrimitiveDMask m @ParameterizedTest @MethodSource("sparseVectorSource") public void mult_A_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResult, Mask mask) { - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(sparseVector, inputMatrix, prevResult.copy(), semiRing, null); - DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(sparseVector, inputMatrix, prevResult.copy(), semiRing, mask); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(sparseVector, inputMatrix, prevResult.copy(), semiRing, null, null); + DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.mult(sparseVector, inputMatrix, prevResult.copy(), semiRing, mask, null); assertMaskedResult(prevResult, found, foundWithMask, mask); } @@ -134,9 +134,9 @@ public void mult_T_A_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResul DMatrixSparseCSC transposed_vector = CommonOps_DSCC.transpose(sparseVector, null, null); DMatrixSparseCSC foundTA = CommonOpsWithSemiRing_DSCC.multTransA( - transposed_vector, inputMatrix, prevResult.copy(), semiRing, null, null, null); + transposed_vector, inputMatrix, prevResult.copy(), semiRing, null, null, null, null); DMatrixSparseCSC foundTAWithMask = CommonOpsWithSemiRing_DSCC.multTransA( - transposed_vector, inputMatrix, prevResult.copy(), semiRing, mask, null, null); + transposed_vector, inputMatrix, prevResult.copy(), semiRing, mask, null, null, null); assertMaskedResult(prevResult, foundTA, foundTAWithMask, mask); } @@ -147,9 +147,9 @@ public void mult_A_T_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResul DMatrixSparseCSC transposed_matrix = CommonOps_DSCC.transpose(inputMatrix, null, null); DMatrixSparseCSC foundTB = CommonOpsWithSemiRing_DSCC.multTransB( - sparseVector, transposed_matrix, prevResult.copy(), semiRing, null, null, null); + sparseVector, transposed_matrix, prevResult.copy(), semiRing, null, null, null, null); DMatrixSparseCSC foundTBWithMask = CommonOpsWithSemiRing_DSCC.multTransB( - sparseVector, transposed_matrix, prevResult.copy(), semiRing, mask, null, null); + sparseVector, transposed_matrix, prevResult.copy(), semiRing, mask, null, null, null); assertMaskedResult(prevResult, foundTB, foundTBWithMask, mask); } @@ -159,9 +159,9 @@ public void mult_A_T_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResul @MethodSource("sparseMatrixSource") public void add_A_B(DMatrixSparseCSC otherMatrix, DMatrixSparseCSC prevResult, Mask mask) { DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add( - 1, otherMatrix, 1, inputMatrix, prevResult.copy(), semiRing, null, null, null); + 1, otherMatrix, 1, inputMatrix, prevResult.copy(), semiRing, null, null, null, null); DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.add( - 1, otherMatrix, 1, inputMatrix, prevResult.copy(), semiRing, mask, null, null); + 1, otherMatrix, 1, inputMatrix, prevResult.copy(), semiRing, mask, null, null, null); assertMaskedResult(prevResult, found, foundWithMask, mask); } @@ -170,9 +170,9 @@ public void add_A_B(DMatrixSparseCSC otherMatrix, DMatrixSparseCSC prevResult, M @MethodSource("sparseMatrixSource") public void elementWiseMult(DMatrixSparseCSC otherMatrix, DMatrixSparseCSC prevResult, Mask mask) { DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult( - otherMatrix, inputMatrix, prevResult.copy(), semiRing, null, null, null); + otherMatrix, inputMatrix, prevResult.copy(), semiRing, null, null, null, null); DMatrixSparseCSC foundWithMask = CommonOpsWithSemiRing_DSCC.elementMult( - otherMatrix, inputMatrix, prevResult.copy(), semiRing, mask, null, null); + otherMatrix, inputMatrix, prevResult.copy(), semiRing, mask, null, null, null); assertMaskedResult(prevResult, found, foundWithMask, mask); } @@ -203,9 +203,8 @@ public void reduceRowWise(boolean negatedMask) { DMatrixRMaj prevResult = DMatrixRMaj.wrap(matrix.numRows, 1, prevPrimitiveResult); Mask mask = DMasks.of(prevResult, negatedMask); - DBinaryOperator first = (x, y) -> x; DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), null, null); - DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), mask, first); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), mask, null); assertMaskedResult(prevResult, result, resultWithMask, mask); } @@ -226,9 +225,8 @@ public void reduceColumnWise(boolean negatedMask) { DMatrixRMaj prevResult = DMatrixRMaj.wrap(1, matrix.numCols, prevPrimitiveResult); Mask mask = DMasks.of(prevResult, negatedMask); - DBinaryOperator first = (x, y) -> x; DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), null, null); - DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), mask, first); + DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), mask, null); assertMaskedResult(prevResult, result, resultWithMask, mask); } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java index 77a76d2fc..3004d634d 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMatrixMatrixMultWithSemiRing_DSCC.java @@ -44,7 +44,7 @@ public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { vector.set(0, 3, 0.5); vector.set(0, 5, 0.6); - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.mult(vector, inputMatrix, null, semiRing, null, null); assertEquals(expected[0], found.get(0, 0)); assertEquals(expected[1], found.get(0, 2)); @@ -55,7 +55,7 @@ public void mult_v_A(String desc, DSemiRing semiRing, double[] expected) { public void mult_AT_B(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransA(matrix, otherMatrix, null, semiRing, null, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransA(matrix, otherMatrix, null, semiRing, null, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.multTransA(matrix, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); @@ -66,7 +66,7 @@ public void mult_AT_B(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC oth public void mult_A_BT(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransB(matrix, otherMatrix, null, semiRing, null, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.multTransB(matrix, otherMatrix, null, semiRing, null, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.multTransB(matrix, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); @@ -77,7 +77,7 @@ public void mult_A_BT(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC oth public void elementMult(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, null, semiRing, null, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.elementMult(matrix, otherMatrix, null, semiRing, null, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.elementMult(matrix, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); @@ -88,7 +88,7 @@ public void elementMult(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC o public void add(String desc, DMatrixSparseCSC matrix, DMatrixSparseCSC otherMatrix) { DSemiRing semiRing = DSemiRings.PLUS_TIMES; - DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, matrix, 1, otherMatrix, null, semiRing, null, null, null); + DMatrixSparseCSC found = CommonOpsWithSemiRing_DSCC.add(1, matrix, 1, otherMatrix, null, semiRing, null, null, null, null); DMatrixSparseCSC expected = CommonOps_DSCC.add(1, matrix, 1, otherMatrix, null, null, null); EjmlUnitTests.assertEquals(expected, found); From c4c496eceee65c6b8450897949c0390f01d9cd18 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 11 Aug 2020 22:25:01 +0200 Subject: [PATCH 40/48] Code format and add java doc for accumulator --- .../csc/CommonOpsWithSemiRing_DSCC.java | 84 ++++++++++--------- .../csc/mult/TestMaskedOperators_DSCC.java | 11 ++- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index 067a570de..af4ce418f 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -47,12 +47,13 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul /** * Performs matrix multiplication. output = A*B * - * @param A (Input) Matrix. Not modified. - * @param B (Input) Matrix. Not modified. - * @param output (Output) Storage for results. Data length is increased if increased if insufficient. - * @param mask Mask for specifying which entries should be overwritten - * @param gw (Optional) Storage for internal workspace. Can be null. - * @param gx (Optional) Storage for internal workspace. Can be null. + * @param A (Input) Matrix. Not modified. + * @param B (Input) Matrix. Not modified. + * @param output (Output) Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten + * @param accumulator Operator to combine result with existing entries in output matrix + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @@ -63,7 +64,7 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul // !! important to do before reshape DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numCols) ? output.copy() : null; - output = reshapeOrDeclare(output,A,A.numRows,B.numCols); + output = reshapeOrDeclare(output, A, A.numRows, B.numCols); if (mask != null) { mask.compatible(output); } @@ -80,7 +81,7 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); // !! important to do before reshape DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numCols, B.numCols) ? output.copy() : null; - output = reshapeOrDeclare(output,A,A.numCols,B.numCols); + output = reshapeOrDeclare(output, A, A.numCols, B.numCols); if (mask != null) { mask.compatible(output); } @@ -94,12 +95,13 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B * Performs matrix multiplication. output = A*BT. B needs to be sorted and will be sorted if it * has not already been sorted. * - * @param A (Input) Matrix. Not modified. - * @param B (Input) Matrix. Value not modified but indicies will be sorted if not sorted already. - * @param output (Output) Storage for results. Data length is increased if increased if insufficient. - * @param mask Mask for specifying which entries should be overwritten - * @param gw (Optional) Storage for internal workspace. Can be null. - * @param gx (Optional) Storage for internal workspace. Can be null. + * @param A (Input) Matrix. Not modified. + * @param B (Input) Matrix. Value not modified but indicies will be sorted if not sorted already. + * @param output (Output) Storage for results. Data length is increased if increased if insufficient. + * @param mask Mask for specifying which entries should be overwritten + * @param accumulator Operator to combine result with existing entries in output matrix + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @@ -122,15 +124,15 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B /** * Performs matrix multiplication. output = A*B * - * @param A Matrix - * @param B Dense Matrix + * @param A Matrix + * @param B Dense Matrix * @param output Dense Matrix */ public static DMatrixRMaj mult(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { if (A.numCols != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - output = reshapeOrDeclare(output,A.numRows,B.numCols); + output = reshapeOrDeclare(output, A.numRows, B.numCols); ImplSparseSparseMultWithSemiRing_DSCC.mult(A, B, output, semiRing); @@ -150,8 +152,8 @@ public static void multAdd(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixR /** * Performs matrix multiplication. output = AT*B * - * @param A Matrix - * @param B Dense Matrix + * @param A Matrix + * @param B Dense Matrix * @param output Dense Matrix */ public static DMatrixRMaj multTransA(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { @@ -178,8 +180,8 @@ public static void multAddTransA(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj /** * Performs matrix multiplication. output = A*BT * - * @param A Matrix - * @param B Dense Matrix + * @param A Matrix + * @param B Dense Matrix * @param output Dense Matrix */ public static DMatrixRMaj multTransB(DMatrixSparseCSC A, DMatrixRMaj B, @Nullable DMatrixRMaj output, DSemiRing semiRing) { @@ -207,8 +209,8 @@ public static void multAddTransB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj /** * Performs matrix multiplication. output = AT*BT * - * @param A Matrix - * @param B Dense Matrix + * @param A Matrix + * @param B Dense Matrix * @param output Dense Matrix */ public static DMatrixRMaj multTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj output, DSemiRing semiRing) { @@ -234,21 +236,23 @@ public static void multAddTransAB(DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj } // sparse versions again + /** * Performs matrix addition:
* output = αA + βB * - * @param alpha scalar value multiplied against A - * @param A Matrix - * @param beta scalar value multiplied against B - * @param B Matrix - * @param output (Optional) Output matrix. - * @param mask Mask for specifying which entries should be overwritten - * @param gw (Optional) Storage for internal workspace. Can be null. - * @param gx (Optional) Storage for internal workspace. Can be null. + * @param alpha scalar value multiplied against A + * @param A Matrix + * @param beta scalar value multiplied against B + * @param B Matrix + * @param output (Optional) Output matrix. + * @param mask Mask for specifying which entries should be overwritten + * @param accumulator Operator to combine result with existing entries in output matrix + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta, DMatrixSparseCSC B, @Nullable DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows || A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; @@ -266,15 +270,17 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta * Performs an element-wise multiplication.
* output[i,j] = A[i,j]*B[i,j]
* All matrices must have the same shape. - * @param A (Input) Matrix. - * @param B (Input) Matrix - * @param output (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. - * @param mask Mask for specifying which entries should be overwritten - * @param gw (Optional) Storage for internal workspace. Can be null. - * @param gx (Optional) Storage for internal workspace. Can be null. + * + * @param A (Input) Matrix. + * @param B (Input) Matrix + * @param output (Output) Matrix. data array is grown to min(A.nz_length,B.nz_length), resulting a in a large speed boost. + * @param mask Mask for specifying which entries should be overwritten + * @param accumulator Operator to combine result with existing entries in output matrix + * @param gw (Optional) Storage for internal workspace. Can be null. + * @param gx (Optional) Storage for internal workspace. Can be null. */ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output, DSemiRing semiRing, - @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { + @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index a8df7c3b5..bdae78a78 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -80,8 +80,8 @@ private static Stream sparseMatrixSource() { otherMatrix.set(0, 5, 0.6); DMatrixSparseCSC prevResult = new DMatrixSparseCSC(7, 7); - prevResult.set(0,0, 99); - prevResult.set(0,3, 42); + prevResult.set(0, 0, 99); + prevResult.set(0, 3, 42); return Stream.of( Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, true)), @@ -238,16 +238,15 @@ private void assertMaskedResult(DMatrixSparseCSC prevResult, DMatrixSparseCSC fo it.forEachRemaining(value -> { if (mask.isSet(value.row, value.col)) { assertEquals(found.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field should have been computed"); - } - else { - assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was overwritten"); + } else { + assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was overwritten"); } }); // checking that untouched cells are still present prevResult.createCoordinateIterator().forEachRemaining(value -> { if (!mask.isSet(value.row, value.col)) { - assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was deleted"); + assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was deleted"); } }); } From c55312b21b60840bbe5f6e0f7c6f40240849062a Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 13 Aug 2020 11:41:34 +0200 Subject: [PATCH 41/48] Add a replace flag and use a builder for masks --- main/ejml-core/src/org/ejml/masks/DMasks.java | 31 ++++---------- main/ejml-core/src/org/ejml/masks/Mask.java | 7 +++- .../src/org/ejml/masks/MaskBuilder.java | 36 ++++++++++++++++ .../src/org/ejml/masks/PrimitiveDMask.java | 42 ++++++++++++------- .../src/org/ejml/masks/SparseDMask.java | 27 +++++++++--- .../org/ejml/masks/SparseStructuralMask.java | 17 +++++++- .../org/ejml/masks/TestPrimitiveDMasks.java | 10 +++-- .../test/org/ejml/masks/TestSparseDMasks.java | 5 ++- .../ejml/masks/TestSparseStructuralMasks.java | 5 ++- .../csc/mult/TestMaskedOperators_DSCC.java | 25 +++++------ 10 files changed, 139 insertions(+), 66 deletions(-) create mode 100644 main/ejml-core/src/org/ejml/masks/MaskBuilder.java diff --git a/main/ejml-core/src/org/ejml/masks/DMasks.java b/main/ejml-core/src/org/ejml/masks/DMasks.java index 1a4add7b8..329dc13b1 100644 --- a/main/ejml-core/src/org/ejml/masks/DMasks.java +++ b/main/ejml-core/src/org/ejml/masks/DMasks.java @@ -21,38 +21,23 @@ import org.ejml.data.*; /** - * Helper class to create the corresponding mask based on a matrix or primitive array + * Helper class to get the corresponding mask builder based on a matrix or primitive array */ public class DMasks { - // TODO: use builder? - - public static Mask of(double[] values, boolean negated) { - return of(values, negated, 0); - } - - public static Mask of(double[] values, boolean negated, double zeroElement) { - return new PrimitiveDMask(values, negated, zeroElement); + public static PrimitiveDMask.Builder builder(double[] values) { + return new PrimitiveDMask.Builder(values); } - public static Mask of(DMatrixD1 matrix, boolean negated) { - return of(matrix, negated, 0); + public static PrimitiveDMask.Builder builder(DMatrixD1 matrix) { + return new PrimitiveDMask.Builder(matrix.data).withNumCols(matrix.numCols); } - public static Mask of(DMatrixD1 matrix, boolean negated, double zeroElement) { - return new PrimitiveDMask(matrix.data, matrix.numCols, negated, zeroElement); - } - - public static Mask of(DMatrixSparseCSC matrix, boolean negated, boolean structural, double zeroElement){ + public static MaskBuilder builder(DMatrixSparseCSC matrix, boolean structural){ if (structural) { - return new SparseStructuralMask(matrix, negated); + return new SparseStructuralMask.Builder(matrix); } else { - return new SparseDMask(matrix, negated, zeroElement); + return new SparseDMask.Builder(matrix); } } - - // structural masks cannot have a zeroElement - public static Mask of(DMatrixSparseCSC matrix, boolean negated, boolean structural) { - return of(matrix, negated, structural, 0); - } } diff --git a/main/ejml-core/src/org/ejml/masks/Mask.java b/main/ejml-core/src/org/ejml/masks/Mask.java index 9fe4dbe5c..2d70fefbe 100644 --- a/main/ejml-core/src/org/ejml/masks/Mask.java +++ b/main/ejml-core/src/org/ejml/masks/Mask.java @@ -29,8 +29,13 @@ public abstract class Mask { // useful for sparse matrices, as actual negation would be costly and result in dense masks public final boolean negated; - public Mask(boolean negated) { + // whether the result should replace the initial result (otherwise merge them) + // same as the "GrB_REPLACE" descriptor + public final boolean replace; + + public Mask(boolean negated, boolean replace) { this.negated = negated; + this.replace = replace; } public abstract boolean isSet(int row, int col); diff --git a/main/ejml-core/src/org/ejml/masks/MaskBuilder.java b/main/ejml-core/src/org/ejml/masks/MaskBuilder.java new file mode 100644 index 000000000..b91cf4386 --- /dev/null +++ b/main/ejml-core/src/org/ejml/masks/MaskBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.masks; + +public abstract class MaskBuilder { + protected boolean negated = false; + protected boolean replace = true; + + public MaskBuilder withNegated(boolean negated) { + this.negated = negated; + return this; + } + + public MaskBuilder withReplace(boolean replace) { + this.replace = replace; + return this; + } + + public abstract MASK build(); +} diff --git a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java index d848430e9..0ad72d451 100644 --- a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java +++ b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java @@ -26,21 +26,9 @@ public class PrimitiveDMask extends Mask { // private final double zeroElement; - public PrimitiveDMask(double[] values, boolean negated) { - this(values, 1, negated, 0); - } - - public PrimitiveDMask(double[] values, boolean negated, double zeroElement) { - this(values, 1, negated, zeroElement); - } - - public PrimitiveDMask(double[] values, int numCols, boolean negated) { - this(values, numCols, negated, 0); - } - - public PrimitiveDMask(double[] values, int numCols, boolean negated, double zeroElement) { + public PrimitiveDMask(double[] values, int numCols, boolean negated, boolean replace, double zeroElement) { // for dense structures they cannot be used for structural masks - super(negated); + super(negated, replace); this.values = values; this.numCols = numCols; this.zeroElement = zeroElement; @@ -65,4 +53,30 @@ public int getNumRows() { public boolean isSet(int index) { return negated ^ (values[index] != zeroElement); } + + public static class Builder extends MaskBuilder { + private double[] values; + private int numCols = 1; + private double zeroElement = 0; + + public Builder(double[] values) { + this.values = values; + } + + public Builder withNumCols(int numCols) { + this.numCols = numCols; + return this; + } + + public Builder withZeroElement(double zeroElement) { + this.zeroElement = zeroElement; + return this; + } + + @Override + public PrimitiveDMask build() { + return new PrimitiveDMask(values, numCols, negated, replace, zeroElement); + } + } } + diff --git a/main/ejml-core/src/org/ejml/masks/SparseDMask.java b/main/ejml-core/src/org/ejml/masks/SparseDMask.java index 7c8a97c0e..3dfb06c3d 100644 --- a/main/ejml-core/src/org/ejml/masks/SparseDMask.java +++ b/main/ejml-core/src/org/ejml/masks/SparseDMask.java @@ -24,12 +24,8 @@ public class SparseDMask extends Mask { protected final DMatrixSparseCSC matrix; protected final double zeroElement; - public SparseDMask(DMatrixSparseCSC matrix, boolean negated) { - this(matrix, negated, 0); - } - - public SparseDMask(DMatrixSparseCSC matrix, boolean negated, double zeroElement) { - super(negated); + public SparseDMask(DMatrixSparseCSC matrix, boolean negated, boolean replace, double zeroElement) { + super(negated, replace); this.matrix = matrix; this.zeroElement = zeroElement; } @@ -48,4 +44,23 @@ public int getNumCols() { public int getNumRows() { return matrix.numRows; } + + public static class Builder extends MaskBuilder { + private DMatrixSparseCSC matrix; + private double zeroElement = 0; + + public Builder(DMatrixSparseCSC matrix) { + this.matrix = matrix; + } + + public Builder withZeroElement(double zeroElement) { + this.zeroElement = zeroElement; + return this; + } + + @Override + public SparseDMask build() { + return new SparseDMask(matrix, negated, replace, zeroElement); + } + } } diff --git a/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java b/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java index 05beccce7..6c08ab274 100644 --- a/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java +++ b/main/ejml-core/src/org/ejml/masks/SparseStructuralMask.java @@ -27,8 +27,8 @@ public class SparseStructuralMask extends Mask { private final MatrixSparse matrix; - public SparseStructuralMask(MatrixSparse matrix, boolean negated) { - super(negated); + public SparseStructuralMask(MatrixSparse matrix, boolean negated, boolean replace) { + super(negated, replace); this.matrix = matrix; } @@ -46,4 +46,17 @@ public int getNumCols() { public int getNumRows() { return matrix.getNumRows(); } + + public static class Builder extends MaskBuilder { + private MatrixSparse matrix; + + public Builder(MatrixSparse matrix) { + this.matrix = matrix; + } + + @Override + public SparseStructuralMask build() { + return new SparseStructuralMask(matrix, negated, replace); + } + } } diff --git a/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java b/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java index 6ac133893..5307e6a3b 100644 --- a/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java +++ b/main/ejml-core/test/org/ejml/masks/TestPrimitiveDMasks.java @@ -32,8 +32,9 @@ public class TestPrimitiveDMasks { void testBasedOnPrimitiveArrays() { double[] values = {2, 0, 4, 0, 0, -1}; - PrimitiveDMask mask = new PrimitiveDMask(values, false); - PrimitiveDMask negated_mask = new PrimitiveDMask(values, true); + PrimitiveDMask.Builder maskBuilder = new PrimitiveDMask.Builder(values); + PrimitiveDMask mask = maskBuilder.withNegated(false).build(); + PrimitiveDMask negated_mask = maskBuilder.withNegated(true).build(); boolean[] expected = {true, false, true, false, false, true}; @@ -48,8 +49,9 @@ void testBasedOnDenseMatrix() { int dim = 20; DMatrixRMaj matrix = RandomMatrices_DDRM.rectangle(dim, dim, new Random(42)); - PrimitiveDMask mask = new PrimitiveDMask(matrix.data, matrix.numCols, false); - PrimitiveDMask negated_mask = new PrimitiveDMask(matrix.data, matrix.numCols, true); + PrimitiveDMask.Builder maskBuilder = DMasks.builder(matrix); + PrimitiveDMask mask = maskBuilder.withNegated(false).build(); + PrimitiveDMask negated_mask = maskBuilder.withNegated(true).build(); for (int row = 0; row < dim; row++) { diff --git a/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java b/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java index 12197b602..f0494a83a 100644 --- a/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java +++ b/main/ejml-core/test/org/ejml/masks/TestSparseDMasks.java @@ -33,8 +33,9 @@ void testSparseMasks() { int dim = 20; DMatrixSparseCSC matrix = RandomMatrices_DSCC.rectangle(dim, dim, 5, new Random(42)); - Mask mask = new SparseDMask(matrix, false); - Mask negated_mask = new SparseDMask(matrix, true); + SparseStructuralMask.Builder builder = new SparseStructuralMask.Builder(matrix); + Mask mask = builder.withNegated(false).build(); + Mask negated_mask = builder.withNegated(true).build(); var it = matrix.createCoordinateIterator(); diff --git a/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java b/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java index 8a6e3b1c0..b47ebb324 100644 --- a/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java +++ b/main/ejml-core/test/org/ejml/masks/TestSparseStructuralMasks.java @@ -33,8 +33,9 @@ void testSparseStructuralMasks() { int dim = 20; DMatrixSparseCSC matrix = RandomMatrices_DSCC.rectangle(dim, dim, 5, new Random(42)); - Mask mask = new SparseStructuralMask(matrix, false); - Mask negated_mask = new SparseStructuralMask(matrix, true); + SparseStructuralMask.Builder builder = new SparseStructuralMask.Builder(matrix); + Mask mask = builder.withNegated(false).build(); + Mask negated_mask = builder.withNegated(true).build(); var it = matrix.createCoordinateIterator(); diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index bdae78a78..21cec8a11 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -52,9 +52,10 @@ private static Stream primitiveVectorSource() { prevResult[3] = 99; prevResult[0] = 42; + PrimitiveDMask.Builder maskBuilder = new PrimitiveDMask.Builder(prevResult); return Stream.of( - Arguments.of(inputVector, prevResult, new PrimitiveDMask(prevResult, true)), - Arguments.of(inputVector, prevResult, new PrimitiveDMask(prevResult, false)) + Arguments.of(inputVector, prevResult, maskBuilder.withNegated(true).build()), + Arguments.of(inputVector, prevResult, maskBuilder.withNegated(false).build()) ); } @@ -67,10 +68,10 @@ private static Stream sparseVectorSource() { prevResult.set(0, 2, 99); return Stream.of( - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, true)), - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, false)), - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, false)), - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, true)) + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(true).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(true).build()) ); } @@ -84,10 +85,10 @@ private static Stream sparseMatrixSource() { prevResult.set(0, 3, 42); return Stream.of( - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, true)), - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, false)), - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, false, false)), - Arguments.of(otherMatrix, prevResult, DMasks.of(prevResult, true, true)) + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(true).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(true).build()) ); } @@ -201,7 +202,7 @@ public void reduceRowWise(boolean negatedMask) { prevPrimitiveResult[0] = 99; DMatrixRMaj prevResult = DMatrixRMaj.wrap(matrix.numRows, 1, prevPrimitiveResult); - Mask mask = DMasks.of(prevResult, negatedMask); + Mask mask = DMasks.builder(prevResult).withNegated(negatedMask).build(); DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), null, null); DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), mask, null); @@ -223,7 +224,7 @@ public void reduceColumnWise(boolean negatedMask) { prevPrimitiveResult[0] = 99; DMatrixRMaj prevResult = DMatrixRMaj.wrap(1, matrix.numCols, prevPrimitiveResult); - Mask mask = DMasks.of(prevResult, negatedMask); + Mask mask = DMasks.builder(prevResult).withNegated(negatedMask).build(); DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), null, null); DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), mask, null); From 8537ef6bcd4c622e346af0c345d70e75357d5cc7 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 13 Aug 2020 14:11:55 +0200 Subject: [PATCH 42/48] Support accumulator for matrixXprimitive ops --- main/ejml-core/src/org/ejml/UtilEjml.java | 10 --- .../src/org/ejml/masks/PrimitiveDMask.java | 11 +++ .../csc/CommonOpsWithSemiRing_DSCC.java | 13 ++-- .../org/ejml/sparse/csc/CommonOps_DSCC.java | 7 +- .../org/ejml/sparse/csc/MaskUtil_DSCC.java | 44 ++++++++++- .../MatrixVectorMultWithSemiRing_DSCC.java | 73 +++++++++++-------- .../csc/mult/TestMaskedOperators_DSCC.java | 53 +++++++------- 7 files changed, 132 insertions(+), 79 deletions(-) diff --git a/main/ejml-core/src/org/ejml/UtilEjml.java b/main/ejml-core/src/org/ejml/UtilEjml.java index 410f1ba98..7d23b60d8 100644 --- a/main/ejml-core/src/org/ejml/UtilEjml.java +++ b/main/ejml-core/src/org/ejml/UtilEjml.java @@ -22,7 +22,6 @@ import org.ejml.interfaces.linsol.LinearSolver; import org.ejml.interfaces.linsol.LinearSolverDense; import org.ejml.interfaces.linsol.LinearSolverSparse; -import org.ejml.masks.Mask; import org.ejml.ops.ConvertDMatrixStruct; import org.ejml.ops.ConvertFMatrixStruct; @@ -550,13 +549,4 @@ public static boolean hasNullableArgument(Method func) { Annotation last = lastArray[lastArray.length-1]; return last.toString().contains("Nullable"); } - - /** - * Check if initialOutput matrix needs to be cached for later merge with actual result - * (in order to prevent deleting entries which shouldnt be overwritten, e.g. !mask.isSet()) - */ - public static boolean useInitialOutput(Mask mask, Matrix initialOutput, int numRows, int numCols) { - // check for dimensions as an indicator (TODO check if actually this is not misleading .. replace by "replace" flag) - return mask != null && initialOutput != null && initialOutput.getNumRows() == numRows && initialOutput.getNumCols() == numCols; - } } diff --git a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java index 0ad72d451..57fa5a232 100644 --- a/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java +++ b/main/ejml-core/src/org/ejml/masks/PrimitiveDMask.java @@ -18,6 +18,8 @@ package org.ejml.masks; +import org.ejml.MatrixDimensionException; + public class PrimitiveDMask extends Mask { // TODO: Check if creating dense boolean mask is worth it (currently converting to boolean on the fly) private final double[] values; @@ -54,6 +56,15 @@ public boolean isSet(int index) { return negated ^ (values[index] != zeroElement); } + public void compatible(double[] vector) { + if (vector.length != values.length) { + throw new MatrixDimensionException(String.format( + "Mask of length %d cannot be applied to vector of lenght %d", + values.length, vector.length + )); + } + } + public static class Builder extends MaskBuilder { private double[] values; private int numCols = 1; diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java index af4ce418f..290b1e286 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOpsWithSemiRing_DSCC.java @@ -19,7 +19,6 @@ package org.ejml.sparse.csc; import org.ejml.MatrixDimensionException; -import org.ejml.UtilEjml; import org.ejml.data.DGrowArray; import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; @@ -62,7 +61,7 @@ public static DMatrixSparseCSC mult(DMatrixSparseCSC A, DMatrixSparseCSC B, @Nul throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); // !! important to do before reshape - DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numCols) ? output.copy() : null; + DMatrixSparseCSC initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, B.numCols); if (mask != null) { @@ -80,7 +79,7 @@ public static DMatrixSparseCSC multTransA(DMatrixSparseCSC A, DMatrixSparseCSC B if (A.numRows != B.numRows) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); // !! important to do before reshape - DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numCols, B.numCols) ? output.copy() : null; + DMatrixSparseCSC initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numCols, B.numCols); if (mask != null) { mask.compatible(output); @@ -108,7 +107,7 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, B.numRows) ? output.copy() : null; + DMatrixSparseCSC initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, B.numRows); if (mask != null) { mask.compatible(output); @@ -119,7 +118,7 @@ public static DMatrixSparseCSC multTransB(DMatrixSparseCSC A, DMatrixSparseCSC B } - // sparse-dense variations + // sparse-dense variations (does not support masked yet. look at primitive versions for vector variants) /** * Performs matrix multiplication. output = A*B @@ -255,7 +254,7 @@ public static DMatrixSparseCSC add(double alpha, DMatrixSparseCSC A, double beta @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numRows != B.numRows || A.numCols != B.numCols) throw new MatrixDimensionException("Inconsistent matrix shapes. " + stringShapes(A, B)); - DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; + DMatrixSparseCSC initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, A.numCols); if (mask != null) { mask.compatible(output); @@ -283,7 +282,7 @@ public static DMatrixSparseCSC elementMult(DMatrixSparseCSC A, DMatrixSparseCSC @Nullable Mask mask, @Nullable DBinaryOperator accumulator, @Nullable IGrowArray gw, @Nullable DGrowArray gx) { if (A.numCols != B.numCols || A.numRows != B.numRows) throw new MatrixDimensionException("All inputs must have the same number of rows and columns. " + stringShapes(A, B)); - DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, A.numRows, A.numCols) ? output.copy() : null; + DMatrixSparseCSC initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.copy() : null; output = reshapeOrDeclare(output, A, A.numRows, A.numCols); if (mask != null) { mask.compatible(output); diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java index a78fad900..4844a3954 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/CommonOps_DSCC.java @@ -19,7 +19,6 @@ package org.ejml.sparse.csc; import org.ejml.MatrixDimensionException; -import org.ejml.UtilEjml; import org.ejml.data.DGrowArray; import org.ejml.data.DMatrixRMaj; import org.ejml.data.DMatrixSparseCSC; @@ -1898,7 +1897,7 @@ public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func // !! FIXME: apply also calculates unneeded fields .. this can produce wrong results with another accumulator than "first" public static DMatrixSparseCSC apply(DMatrixSparseCSC input, DUnaryOperator func, @Nullable DMatrixSparseCSC output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { - DMatrixSparseCSC initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, input.numCols) ? output.copy() : null; + DMatrixSparseCSC initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accum, output) ? output.copy() : null; // set correct structure if (output == null) { output = input.createLike(); @@ -1964,7 +1963,7 @@ public static double reduceScalar(DMatrixSparseCSC input, DBinaryOperator func) */ public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, @Nullable DMatrixRMaj output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { - DMatrixRMaj initialOutput = UtilEjml.useInitialOutput(mask, output, 1, input.numCols) ? output.copy() : null; + DMatrixRMaj initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accum, output) ? output.copy() : null; output = reshapeOrDeclare(output, 1, input.numCols); if (mask != null) { mask.compatible(output); @@ -2008,7 +2007,7 @@ public static DMatrixRMaj reduceColumnWise(DMatrixSparseCSC input, double initVa */ public static DMatrixRMaj reduceRowWise(DMatrixSparseCSC input, double initValue, DBinaryOperator func, @Nullable DMatrixRMaj output, @Nullable Mask mask, @Nullable DBinaryOperator accum) { - DMatrixRMaj initialOutput = UtilEjml.useInitialOutput(mask, output, input.numRows, 1) ? output.copy() : null; + DMatrixRMaj initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accum, output) ? output.copy() : null; output = reshapeOrDeclare(output, input.numRows, 1); if (mask != null) { mask.compatible(output); diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index 8b6382c43..3955ed985 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -18,11 +18,9 @@ package org.ejml.sparse.csc; -import org.ejml.data.DGrowArray; -import org.ejml.data.DMatrixRMaj; -import org.ejml.data.DMatrixSparseCSC; -import org.ejml.data.IGrowArray; +import org.ejml.data.*; import org.ejml.masks.Mask; +import org.ejml.masks.PrimitiveDMask; import org.ejml.ops.DBinaryOperator; import javax.annotation.Nullable; @@ -65,6 +63,7 @@ static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, for (int col = 0; col < output.getNumCols(); col++) { for (int row = 0; row < output.numRows; row++) { if (mask.isSet(row, col)) { + // TODO: use accum == SECOND by default (like above) if (accum != null) { // combine previous value and computed value output.unsafe_set(row, col, accum.apply(output.get(row, col), initialOutput.get(row, col))); @@ -80,6 +79,26 @@ static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, return output; } + public static double[] combineOutputs(@Nullable double[] initialOutput, double[] output, @Nullable PrimitiveDMask mask, @Nullable DBinaryOperator accum) { + if (initialOutput != null) { + if(accum == null) { + // e.g. just take the newly computed value + accum = SECOND; + } + + for (int i = 0; i < output.length; i++) { + if (mask == null || mask.isSet(i)) { + output[i] = accum.apply(initialOutput[i], output[i]); + } + else { + output[i] = initialOutput[i]; + } + } + } + + return output; + } + /** * Performs matrix addition:
* C = A + B @@ -151,4 +170,21 @@ public static void multAddColA(DMatrixSparseCSC A, int colA, } } } + + // TODO: remove replace flag and just decide based on accumulator and intialOutput? (replace flag seems redundant) + // drawback: user has to specify accumulator everytime (e.g. not default to SECOND) + + /** + * Check if initialOutput matrix needs to be cached for later merge with actual result + * + * ! mask.replace takes precedence before existing accumlator + */ + public static boolean useInitialOutput(Mask mask, DBinaryOperator accumulator, Matrix initialOutput) { + return initialOutput != null && ((mask != null && !mask.replace) || (accumulator != null && mask == null)); + } + + // primitive version + public static boolean useInitialOutput(Mask mask, DBinaryOperator accumulator, double[] initialOutput) { + return initialOutput != null && ((mask != null && !mask.replace) || (accumulator != null && mask == null)); + } } diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java index 291ed4aef..b5a91e3ef 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/mult/MatrixVectorMultWithSemiRing_DSCC.java @@ -20,7 +20,9 @@ import org.ejml.data.DMatrixSparseCSC; import org.ejml.masks.PrimitiveDMask; +import org.ejml.ops.DBinaryOperator; import org.ejml.ops.DSemiRing; +import org.ejml.sparse.csc.MaskUtil_DSCC; import javax.annotation.Nullable; import java.util.Arrays; @@ -30,65 +32,76 @@ */ public class MatrixVectorMultWithSemiRing_DSCC { /** - * c = A*b + * output = A*b * * @param A (Input) Matrix * @param b (Input) vector - * @param c (Output) vector + * @param output (Output) vector * @param mask Mask for specifying which entries should be overwritten */ - public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing semiRing, @Nullable PrimitiveDMask mask) { - if (mask == null) { - Arrays.fill(c, semiRing.add.id); - } else { - for (int i = 0; i < c.length; i++) { - if (mask == null || mask.isSet(i)) { - c[i] = semiRing.add.id; - } - } + public static double[] mult(DMatrixSparseCSC A, double b[], double output[], DSemiRing semiRing, + @Nullable PrimitiveDMask mask, @Nullable DBinaryOperator accumulator) { + double[] initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.clone() : null; + if (mask != null) { + mask.compatible(output); } - multAdd(A, b, c, semiRing, mask); + + Arrays.fill(output, semiRing.add.id); + + return multAdd(A, b, output, initialOutput, semiRing, mask, accumulator); } - public static void mult(DMatrixSparseCSC A, double b[], double c[], DSemiRing semiRing) { - mult(A, b, c, semiRing, null); + public static double[] mult(DMatrixSparseCSC A, double b[], double output[], DSemiRing semiRing) { + return mult(A, b, output, semiRing, null, null); } /** - * c = c + A*b + * output = initialOutput + A*b + * ! difference to normal `multAdd` without semiRing: apply + on result of A*b * - * @param A (Input) Matrix - * @param b (Input) vector - * @param c (Output) vector - * @param mask Mask for specifying which entries should be overwritten + * @param A (Input) Matrix + * @param b (Input) vector + * @param output (Output) vector + * @param mask Mask for specifying which entries should be overwritten * @param semiRing + * @param accumulator (Optional) accumulator for output + (A*b), elso use `add` from the semiRing */ - public static void multAdd(DMatrixSparseCSC A, double[] b, double[] c, DSemiRing semiRing, @Nullable PrimitiveDMask mask) { + public static double[] multAdd(DMatrixSparseCSC A, double[] b, double[] output, @Nullable double[] initialOutput, + DSemiRing semiRing, @Nullable PrimitiveDMask mask, @Nullable DBinaryOperator accumulator) { for (int k = 0; k < A.numCols; k++) { int idx0 = A.col_idx[k]; int idx1 = A.col_idx[k + 1]; for (int indexA = idx0; indexA < idx1; indexA++) { if (mask == null || mask.isSet(A.nz_rows[indexA])) { - c[A.nz_rows[indexA]] = semiRing.add.func.apply( - c[A.nz_rows[indexA]], + output[A.nz_rows[indexA]] = semiRing.add.func.apply( + output[A.nz_rows[indexA]], semiRing.mult.func.apply(A.nz_values[indexA], b[k])); } } } + + // initialOutput + output + return MaskUtil_DSCC.combineOutputs(initialOutput, output, mask, accumulator); } /** - * c = aT*B + * output = aT*B * * @param a (Input) vector * @param B (Input) Matrix - * @param c (Output) vector + * @param output (Output) vector * @param mask Mask for specifying which entries should be overwritten */ - public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing, @Nullable PrimitiveDMask mask) { + public static double[] mult(double a[], DMatrixSparseCSC B, double output[], DSemiRing semiRing, + @Nullable PrimitiveDMask mask, @Nullable DBinaryOperator accumulator) { + double[] initialOutput = MaskUtil_DSCC.useInitialOutput(mask, accumulator, output) ? output.clone() : null; + if (mask != null) { + mask.compatible(output); + } + for (int k = 0; k < B.numCols; k++) { - if(mask == null || mask.isSet(k)) { + if (mask == null || mask.isSet(k)) { int idx0 = B.col_idx[k]; int idx1 = B.col_idx[k + 1]; @@ -97,13 +110,15 @@ public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing se for (int indexB = idx0; indexB < idx1; indexB++) { sum = semiRing.add.func.apply(sum, semiRing.mult.func.apply(a[B.nz_rows[indexB]], B.nz_values[indexB])); } - c[k] = sum; + output[k] = sum; } } + + return MaskUtil_DSCC.combineOutputs(initialOutput, output, mask, accumulator); } - public static void mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing) { - mult(a, B, c, semiRing, null); + public static double[] mult(double a[], DMatrixSparseCSC B, double c[], DSemiRing semiRing) { + return mult(a, B, c, semiRing, null, null); } /** diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java index 21cec8a11..9e6e09728 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/mult/TestMaskedOperators_DSCC.java @@ -39,7 +39,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class TestMaskedOperators_DSCC extends BaseTestMatrixMatrixOpsWithSemiRing_DSCC { - + // TODO test with replace = false and generate all possible masks (not hard-coded) + // test accumulator static DSemiRing semiRing = DSemiRings.PLUS_TIMES; private static Stream primitiveVectorSource() { @@ -49,13 +50,14 @@ private static Stream primitiveVectorSource() { inputVector[0] = 0.6; double[] prevResult = new double[7]; - prevResult[3] = 99; + prevResult[2] = 99; prevResult[0] = 42; PrimitiveDMask.Builder maskBuilder = new PrimitiveDMask.Builder(prevResult); + return Stream.of( - Arguments.of(inputVector, prevResult, maskBuilder.withNegated(true).build()), - Arguments.of(inputVector, prevResult, maskBuilder.withNegated(false).build()) + Arguments.of(inputVector, prevResult, maskBuilder.withNegated(true).withReplace(false).build()), + Arguments.of(inputVector, prevResult, maskBuilder.withNegated(false).withReplace(false).build()) ); } @@ -68,10 +70,10 @@ private static Stream sparseVectorSource() { prevResult.set(0, 2, 99); return Stream.of( - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(true).build()), - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(false).build()), - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(false).build()), - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(true).build()) + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(true).withReplace(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(false).withReplace(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(false).withReplace(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(true).withReplace(false).build()) ); } @@ -85,10 +87,10 @@ private static Stream sparseMatrixSource() { prevResult.set(0, 3, 42); return Stream.of( - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(true).build()), - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(false).build()), - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(false).build()), - Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(true).build()) + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(true).withReplace(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(false).withReplace(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, false).withNegated(false).withReplace(false).build()), + Arguments.of(otherMatrix, prevResult, DMasks.builder(prevResult, true).withNegated(true).withReplace(false).build()) ); } @@ -98,8 +100,8 @@ public void mult_v_A(double[] inputVector, double[] prevResult, PrimitiveDMask m double[] found = prevResult.clone(); double[] foundWithMask = prevResult.clone(); - MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, inputMatrix, found, semiRing); - MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, inputMatrix, foundWithMask, semiRing, mask); + found = MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, inputMatrix, found, semiRing); + foundWithMask = MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, inputMatrix, foundWithMask, semiRing, mask, null); assertMaskedResult(prevResult, found, foundWithMask, mask); } @@ -110,8 +112,8 @@ public void mult_A_v(double[] inputVector, double[] prevResult, PrimitiveDMask m double[] found = prevResult.clone(); double[] foundWithMask = prevResult.clone(); - MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, inputVector, found, semiRing); - MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, inputVector, foundWithMask, semiRing, mask); + found = MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, inputVector, found, semiRing); + foundWithMask = MatrixVectorMultWithSemiRing_DSCC.mult(inputMatrix, inputVector, foundWithMask, semiRing, mask, null); assertMaskedResult(prevResult, found, foundWithMask, mask); } @@ -127,8 +129,6 @@ public void mult_A_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResult, assertMaskedResult(prevResult, found, foundWithMask, mask); } - // Todo: solve problem with non-negated masks - @ParameterizedTest @MethodSource("sparseVectorSource") public void mult_T_A_B(DMatrixSparseCSC sparseVector, DMatrixSparseCSC prevResult, Mask mask) { @@ -202,7 +202,7 @@ public void reduceRowWise(boolean negatedMask) { prevPrimitiveResult[0] = 99; DMatrixRMaj prevResult = DMatrixRMaj.wrap(matrix.numRows, 1, prevPrimitiveResult); - Mask mask = DMasks.builder(prevResult).withNegated(negatedMask).build(); + Mask mask = DMasks.builder(prevResult).withNegated(negatedMask).withReplace(false).build(); DMatrixRMaj result = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), null, null); DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceRowWise(matrix, 0, Double::sum, prevResult.copy(), mask, null); @@ -224,7 +224,7 @@ public void reduceColumnWise(boolean negatedMask) { prevPrimitiveResult[0] = 99; DMatrixRMaj prevResult = DMatrixRMaj.wrap(1, matrix.numCols, prevPrimitiveResult); - Mask mask = DMasks.builder(prevResult).withNegated(negatedMask).build(); + Mask mask = DMasks.builder(prevResult).withNegated(negatedMask).withReplace(false).build(); DMatrixRMaj result = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), null, null); DMatrixRMaj resultWithMask = CommonOps_DSCC.reduceColumnWise(matrix, 0, Double::sum, prevResult.copy(), mask, null); @@ -239,24 +239,27 @@ private void assertMaskedResult(DMatrixSparseCSC prevResult, DMatrixSparseCSC fo it.forEachRemaining(value -> { if (mask.isSet(value.row, value.col)) { assertEquals(found.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field should have been computed"); - } else { + } else if(!mask.replace) { assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was overwritten"); } }); // checking that untouched cells are still present prevResult.createCoordinateIterator().forEachRemaining(value -> { - if (!mask.isSet(value.row, value.col)) { + if (!mask.isSet(value.row, value.col) && !mask.replace) { assertEquals(prevResult.get(value.row, value.col), foundWithMask.get(value.row, value.col), "Field from initial result was deleted"); } + else if (mask.replace) { + assertEquals(found.isAssigned(value.row, value.col), foundWithMask.isAssigned(value.row, value.col)); + } }); } private void assertMaskedResult(DMatrixD1 prevResult, DMatrixD1 found, DMatrixD1 foundWithMask, Mask mask) { for (int row = 0; row < found.getNumRows(); row++) { for (int col = 0; col < found.getNumCols(); col++) { - if (mask.isSet(row, col)) { - assertEquals(found.get(row, col), foundWithMask.get(row, col), "Field should have been computed"); + if (mask.isSet(row, col) || mask.replace) { + assertEquals(found.get(row, col), foundWithMask.get(row, col), "Field should have been computed/Initial result cleared"); } else { assertEquals(prevResult.get(row, col), foundWithMask.get(row, col), "Field from initial result was overwritten"); } @@ -266,7 +269,7 @@ private void assertMaskedResult(DMatrixD1 prevResult, DMatrixD1 found, DMatrixD1 private void assertMaskedResult(double[] prevResult, double[] found, double[] foundWithMask, PrimitiveDMask mask) { for (int i = 0; i < found.length; i++) { - if (mask.isSet(i)) { + if (mask.isSet(i) || mask.replace) { assertEquals(foundWithMask[i], found[i], "Computation differs"); } else { assertEquals(prevResult[i], foundWithMask[i], "Initial result was overwritten"); From 3f4a154fe93e163cb65901240c6b23a94aab87b5 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 13 Aug 2020 14:14:32 +0200 Subject: [PATCH 43/48] Simplify combining dense outputs --- .../src/org/ejml/sparse/csc/MaskUtil_DSCC.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index 3955ed985..ca0c1be0f 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -59,16 +59,16 @@ static DMatrixRMaj combineOutputs(DMatrixRMaj output, DMatrixRMaj initialOutput, if (initialOutput != null) { checkSameShape(initialOutput, output, true); + if(accum == null) { + // e.g. just take the newly computed value + accum = SECOND; + } + // TODO: operate on a bitset/boolean[] here -> also just one for-loop needed for (int col = 0; col < output.getNumCols(); col++) { for (int row = 0; row < output.numRows; row++) { if (mask.isSet(row, col)) { - // TODO: use accum == SECOND by default (like above) - if (accum != null) { - // combine previous value and computed value - output.unsafe_set(row, col, accum.apply(output.get(row, col), initialOutput.get(row, col))); - } - // else output value just keeps as it is + output.unsafe_set(row, col, accum.apply(initialOutput.get(row, col), output.get(row, col))); } else { // just use previous value as it shouldnt be computed in the first place output.unsafe_set(row, col, initialOutput.get(row, col)); From 1e4f62543ed42b337ea1df7c10f64d17944eb7f2 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 13 Aug 2020 22:37:19 +0200 Subject: [PATCH 44/48] Return Mask instead of Object in MaskBuilder --- main/ejml-core/src/org/ejml/masks/MaskBuilder.java | 2 +- main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/ejml-core/src/org/ejml/masks/MaskBuilder.java b/main/ejml-core/src/org/ejml/masks/MaskBuilder.java index b91cf4386..535cf4ce6 100644 --- a/main/ejml-core/src/org/ejml/masks/MaskBuilder.java +++ b/main/ejml-core/src/org/ejml/masks/MaskBuilder.java @@ -18,7 +18,7 @@ package org.ejml.masks; -public abstract class MaskBuilder { +public abstract class MaskBuilder { protected boolean negated = false; protected boolean replace = true; diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java index ca0c1be0f..3ec8d864d 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/MaskUtil_DSCC.java @@ -34,7 +34,7 @@ public class MaskUtil_DSCC { // for applying mask and accumulator (output gets overwritten) // ! assumes that the mask is already applied to the output .. e.g. unset fields not even computed (and not assigned) // ! the mask is only needed for `apply` there the mask is not applied to the result structure before - static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DMatrixSparseCSC initialOutput, @Nullable Mask mask, DBinaryOperator accum) { + public static DMatrixSparseCSC combineOutputs(DMatrixSparseCSC output, DMatrixSparseCSC initialOutput, @Nullable Mask mask, @Nullable DBinaryOperator accum) { if (initialOutput != null) { if(accum == null) { // e.g. just take the newly computed value From 4721dbe2088a3104baa95fc66ab185e4e4628f88 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Thu, 13 Aug 2020 22:38:17 +0200 Subject: [PATCH 45/48] Implement sparse variations of BFS .. dense version is WIP --- .../ejml/sparse/csc/graphAlgos/BFS_DSCC.java | 171 ++++++++++++++++++ .../sparse/csc/graphAlgos/BfsTest_DSCC.java | 93 ++++++++++ 2 files changed, 264 insertions(+) create mode 100644 main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java create mode 100644 main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java new file mode 100644 index 000000000..150dd756c --- /dev/null +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.graphAlgos; + +import org.ejml.data.DGrowArray; +import org.ejml.data.DMatrixSparseCSC; +import org.ejml.data.IGrowArray; +import org.ejml.masks.DMasks; +import org.ejml.masks.Mask; +import org.ejml.masks.PrimitiveDMask; +import org.ejml.ops.DSemiRing; +import org.ejml.ops.DSemiRings; +import org.ejml.sparse.csc.CommonOpsWithSemiRing_DSCC; +import org.ejml.sparse.csc.CommonOps_DSCC; +import org.ejml.sparse.csc.MaskUtil_DSCC; +import org.ejml.sparse.csc.mult.MatrixVectorMultWithSemiRing_DSCC; + +import java.util.Arrays; + +// variants: boolean/parents/level/multi-bfs + sparse/dense result vector +public class BFS_DSCC { + private static DSemiRing getSemiRing(BfsVariation variation) { + return variation == BfsVariation.PARENTS ? DSemiRings.MIN_FIRST : DSemiRings.OR_AND; + } + // TODO: try to reuse working arrays/matrices e.g. gw, gx (and potentially initialOutput vector/array) + + // TODO: also return needed iterations + + // TODO: is the tmp iterationResult really necessary? (just the inputVector could be enough) + + + // FIXME: does not work yet + // TODO: use dense matrix instead of pure primitive array (f.i. to use `apply` and to support MSBFS) + public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int startNode, int maxIterations) { + DSemiRing semiRing = getSemiRing(bfsVariation); + double[] result = new double[adjacencyMatrix.numCols]; + result[startNode] = 1; + + // or use dense matrix and reduceScalar to count non-zero elements + double[] iterationResult = new double[adjacencyMatrix.numCols]; + + if (bfsVariation == BfsVariation.PARENTS) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + double[] inputVector = result.clone(); + + // TODO: is this the best way to detect a fixPoint + for (int iteration = 1; !((iteration == maxIterations) || (Arrays.equals(result, iterationResult))); iteration++) { + // negated -> dont compute values for visited nodes + // replace -> iterationResult is basically the new inputVector + PrimitiveDMask mask = DMasks.builder(result).withNegated(true).withReplace(true).build(); + // FIXME: iterationResult gets wrong + iterationResult = MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, adjacencyMatrix, iterationResult, semiRing, mask, null); + + inputVector = iterationResult.clone(); + + if (bfsVariation == BfsVariation.LEVEL) { + // poor mans apply on primitive array + for (int i = 0; i < iterationResult.length; i++) { + if (iterationResult[i] != semiRing.add.id) { + iterationResult[i] = iteration + 1; + } + } + } + + result = MaskUtil_DSCC.combineOutputs(result, iterationResult.clone(), mask, null); + } + + return result; + } + + public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int[] startNodes, int maxIterations) { + // TODO: use transposed result matrix as startNodes.length << adjacencyMatrix.length + DMatrixSparseCSC result = new DMatrixSparseCSC(startNodes.length, adjacencyMatrix.numCols); + // DMatrixSparseCSC iterationResult = result.createLike(); + + // init result vector + for (int i = 0; i < startNodes.length; i++) { + if (bfsVariation == BfsVariation.PARENTS) { + result.set(0, startNodes[i], i + 1); + } + else { + result.set(0, startNodes[i], 1); + } + } + + DMatrixSparseCSC inputVector = result.copy(); + DMatrixSparseCSC iterationResult = null; + + // for reusing memory + IGrowArray gw = new IGrowArray(); + DGrowArray gx = new DGrowArray(); + + int visitedNodes = startNodes.length; + int prevVisitedNodes = -1; + + DSemiRing semiRing = getSemiRing(bfsVariation); + + // TODO: parents version via using a different semiRing + + boolean isFixPoint = false; + + for (int iteration = 1; (iteration <= maxIterations) && !isFixPoint; iteration++) { + // negated -> dont compute values for visited nodes + // replace -> iterationResult is basically the new inputVector + Mask mask = DMasks.builder(result, true).withNegated(true).withReplace(true).build(); + iterationResult = CommonOpsWithSemiRing_DSCC.mult(inputVector, adjacencyMatrix, iterationResult, semiRing, mask, null, gw, gx); + prevVisitedNodes = visitedNodes; + + if (mask.replace) { + visitedNodes += iterationResult.nz_length; + } + else { + visitedNodes = iterationResult.nz_length; + } + + // set inputVector based on newly discovered nodes + // TODO: do this via an `assign` that supports a mask + inputVector = iterationResult.copy(); + + if (bfsVariation == BfsVariation.LEVEL) { + int currentIteration = iteration + 1; + CommonOps_DSCC.apply(iterationResult, x -> currentIteration); + } + + if (bfsVariation == BfsVariation.PARENTS) { + // TODO: generalize to apply for a (row, col, value) -> newValue + // set value to its own id + for (int col = 0; col < inputVector.numCols; col++) { + int idx = inputVector.col_idx[col]; + int endIdx = inputVector.col_idx[col + 1]; + + for (; idx < endIdx; idx++) { + inputVector.nz_values[idx] = col + 1; + } + } + } + + // combine iterationResult and result (basically poor mans `mask.replace=false`) + result = MaskUtil_DSCC.combineOutputs(result, iterationResult, null, null); + + result.print(); + + isFixPoint = (visitedNodes == prevVisitedNodes) || (visitedNodes == adjacencyMatrix.numCols); + } + + return result; + } + + + public enum BfsVariation { + BOOLEAN, PARENTS, LEVEL + } +} diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java new file mode 100644 index 000000000..e843db47f --- /dev/null +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2020, Peter Abeles. All Rights Reserved. + * + * This file is part of Efficient Java Matrix Library (EJML). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ejml.sparse.csc.graphAlgos; + +import org.ejml.EjmlUnitTests; +import org.ejml.data.DMatrixRMaj; +import org.ejml.data.DMatrixSparseCSC; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.ejml.sparse.csc.graphAlgos.BFS_DSCC.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class BfsTest_DSCC { + DMatrixSparseCSC inputMatrix; + + private static Stream bfsVariantSource() { + + return Stream.of( + Arguments.of(BfsVariation.BOOLEAN, new double[] {1,1,1,1,1,1,1}), + Arguments.of(BfsVariation.LEVEL, new double[] {1,2,3,2,3,4,3}), + Arguments.of(BfsVariation.PARENTS, new double[] {1,1,4,1,2,3,2}) + + ); + } + + @BeforeEach + public void setUp() { + // based on example in http://mit.bme.hu/~szarnyas/grb/graphblas-introduction.pdf + inputMatrix = new DMatrixSparseCSC(7, 7, 12); + inputMatrix.set(0, 1, 1); + inputMatrix.set(0, 3, 1); + inputMatrix.set(1, 4, 1); + inputMatrix.set(1, 6, 1); + inputMatrix.set(2, 5, 1); + inputMatrix.set(3, 0, 0.2); + inputMatrix.set(3, 2, 0.4); + inputMatrix.set(4, 5, 1); + inputMatrix.set(5, 2, 0.5); + inputMatrix.set(6, 2, 1); + inputMatrix.set(6, 3, 1); + inputMatrix.set(6, 4, 1); + } + + @ParameterizedTest + @MethodSource("bfsVariantSource") + public void testSparseVariations(BfsVariation variation, double[] expected) { + int[] startNodes = {0}; + int maxIterations = 20; + DMatrixSparseCSC result = computeSparse(inputMatrix, variation, startNodes, maxIterations); + + DMatrixRMaj expectedMatrix = new DMatrixRMaj(1, inputMatrix.numCols, true, expected); + EjmlUnitTests.assertEquals(expectedMatrix, result); + } + + // ! skipping BfsVariation.PARENTS for now + @ParameterizedTest + @MethodSource("bfsVariantSource") + public void testDenseVariations(BfsVariation variation, double[] expected) { + int startNode = 0; + int maxIterations = 20; + + if (variation != BfsVariation.PARENTS) { + double[] result = computeDense(inputMatrix, variation, startNode, maxIterations); + + System.out.println(Arrays.toString(result)); + assertTrue(Arrays.equals(expected, result)); + } + } +} \ No newline at end of file From bad3904dfdd6b8f1b49af64cac0a9a04a4770d30 Mon Sep 17 00:00:00 2001 From: FlorentinD Date: Tue, 18 Aug 2020 14:39:48 +0200 Subject: [PATCH 46/48] Fix dense bfs variations --- .../ejml/sparse/csc/graphAlgos/BFS_DSCC.java | 63 +++++++++++++------ .../sparse/csc/graphAlgos/BfsTest_DSCC.java | 7 +-- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java index 150dd756c..a5388b7bc 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java @@ -35,40 +35,55 @@ // variants: boolean/parents/level/multi-bfs + sparse/dense result vector public class BFS_DSCC { + private static DSemiRing getSemiRing(BfsVariation variation) { return variation == BfsVariation.PARENTS ? DSemiRings.MIN_FIRST : DSemiRings.OR_AND; } // TODO: try to reuse working arrays/matrices e.g. gw, gx (and potentially initialOutput vector/array) - // TODO: also return needed iterations - // TODO: is the tmp iterationResult really necessary? (just the inputVector could be enough) - // FIXME: does not work yet // TODO: use dense matrix instead of pure primitive array (f.i. to use `apply` and to support MSBFS) public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int startNode, int maxIterations) { DSemiRing semiRing = getSemiRing(bfsVariation); double[] result = new double[adjacencyMatrix.numCols]; - result[startNode] = 1; + Arrays.fill(result, semiRing.add.id); + + if (bfsVariation == BfsVariation.PARENTS) { + result[startNode] = startNode + 1; + } else { + result[startNode] = 1; + } + // or use dense matrix and reduceScalar to count non-zero elements double[] iterationResult = new double[adjacencyMatrix.numCols]; - if (bfsVariation == BfsVariation.PARENTS) { - throw new UnsupportedOperationException("Not yet implemented"); - } + int visitedNodes = 1; + int prevVisitedNodes = -1; double[] inputVector = result.clone(); + boolean isFixPoint = false; + int iteration = 1; - // TODO: is this the best way to detect a fixPoint - for (int iteration = 1; !((iteration == maxIterations) || (Arrays.equals(result, iterationResult))); iteration++) { + for (; (iteration <= maxIterations) && !isFixPoint; iteration++) { // negated -> dont compute values for visited nodes // replace -> iterationResult is basically the new inputVector - PrimitiveDMask mask = DMasks.builder(result).withNegated(true).withReplace(true).build(); - // FIXME: iterationResult gets wrong + PrimitiveDMask mask = DMasks.builder(result).withZeroElement(semiRing.add.id).withNegated(true).withReplace(true).build(); + // clear iterationsResult to only contain newly discovered nodes + Arrays.fill(iterationResult, semiRing.add.id); iterationResult = MatrixVectorMultWithSemiRing_DSCC.mult(inputVector, adjacencyMatrix, iterationResult, semiRing, mask, null); + prevVisitedNodes = visitedNodes; + + // add newly visited nodes + for (int i = 0; i < iterationResult.length; i++) { + if (iterationResult[i] != semiRing.add.id) { + visitedNodes++; + } + } + inputVector = iterationResult.clone(); if (bfsVariation == BfsVariation.LEVEL) { @@ -80,7 +95,18 @@ public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariati } } + if (bfsVariation == BfsVariation.PARENTS) { + for (int i = 0; i < inputVector.length; i++) { + if (inputVector[i] != semiRing.add.id) { + inputVector[i] = i + 1; + } + } + } + + // FIXME: avoid cloning .. have a combine method that writes into the intialResult result = MaskUtil_DSCC.combineOutputs(result, iterationResult.clone(), mask, null); + + isFixPoint = (visitedNodes == prevVisitedNodes) || (visitedNodes == adjacencyMatrix.numCols); } return result; @@ -88,6 +114,7 @@ public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariati public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int[] startNodes, int maxIterations) { // TODO: use transposed result matrix as startNodes.length << adjacencyMatrix.length + // need to transpose result of VxM before combining DMatrixSparseCSC result = new DMatrixSparseCSC(startNodes.length, adjacencyMatrix.numCols); // DMatrixSparseCSC iterationResult = result.createLike(); @@ -95,8 +122,7 @@ public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, B for (int i = 0; i < startNodes.length; i++) { if (bfsVariation == BfsVariation.PARENTS) { result.set(0, startNodes[i], i + 1); - } - else { + } else { result.set(0, startNodes[i], 1); } } @@ -113,11 +139,10 @@ public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, B DSemiRing semiRing = getSemiRing(bfsVariation); - // TODO: parents version via using a different semiRing - boolean isFixPoint = false; + int iteration = 1; - for (int iteration = 1; (iteration <= maxIterations) && !isFixPoint; iteration++) { + for (; (iteration <= maxIterations) && !isFixPoint; iteration++) { // negated -> dont compute values for visited nodes // replace -> iterationResult is basically the new inputVector Mask mask = DMasks.builder(result, true).withNegated(true).withReplace(true).build(); @@ -126,8 +151,7 @@ public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, B if (mask.replace) { visitedNodes += iterationResult.nz_length; - } - else { + } else { visitedNodes = iterationResult.nz_length; } @@ -156,8 +180,7 @@ public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, B // combine iterationResult and result (basically poor mans `mask.replace=false`) result = MaskUtil_DSCC.combineOutputs(result, iterationResult, null, null); - result.print(); - + // TODO dont combine result if its known to be a fixPoint? isFixPoint = (visitedNodes == prevVisitedNodes) || (visitedNodes == adjacencyMatrix.numCols); } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java index e843db47f..e82c2a309 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java @@ -83,11 +83,8 @@ public void testDenseVariations(BfsVariation variation, double[] expected) { int startNode = 0; int maxIterations = 20; - if (variation != BfsVariation.PARENTS) { - double[] result = computeDense(inputMatrix, variation, startNode, maxIterations); + double[] result = computeDense(inputMatrix, variation, startNode, maxIterations); - System.out.println(Arrays.toString(result)); - assertTrue(Arrays.equals(expected, result)); - } + assertTrue(Arrays.equals(expected, result)); } } \ No newline at end of file From 63341e3ea60f979e1e1691d24e745ca9de9a8709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Tue, 25 Aug 2020 11:58:01 +0200 Subject: [PATCH 47/48] Return bfs-result object containing also the needed iterations --- .../{BFS_DSCC.java => Bfs_DSCC.java} | 76 +++++++++++++++++-- .../sparse/csc/graphAlgos/BfsTest_DSCC.java | 32 +++++--- 2 files changed, 92 insertions(+), 16 deletions(-) rename main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/{BFS_DSCC.java => Bfs_DSCC.java} (78%) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java similarity index 78% rename from main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java rename to main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java index a5388b7bc..bc7505d84 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/BFS_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java @@ -34,7 +34,7 @@ import java.util.Arrays; // variants: boolean/parents/level/multi-bfs + sparse/dense result vector -public class BFS_DSCC { +public class Bfs_DSCC { private static DSemiRing getSemiRing(BfsVariation variation) { return variation == BfsVariation.PARENTS ? DSemiRings.MIN_FIRST : DSemiRings.OR_AND; @@ -45,7 +45,7 @@ private static DSemiRing getSemiRing(BfsVariation variation) { // TODO: use dense matrix instead of pure primitive array (f.i. to use `apply` and to support MSBFS) - public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int startNode, int maxIterations) { + public BfsDenseResult computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int startNode, int maxIterations) { DSemiRing semiRing = getSemiRing(bfsVariation); double[] result = new double[adjacencyMatrix.numCols]; Arrays.fill(result, semiRing.add.id); @@ -78,8 +78,8 @@ public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariati prevVisitedNodes = visitedNodes; // add newly visited nodes - for (int i = 0; i < iterationResult.length; i++) { - if (iterationResult[i] != semiRing.add.id) { + for (double v : iterationResult) { + if (v != semiRing.add.id) { visitedNodes++; } } @@ -109,10 +109,10 @@ public static double[] computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariati isFixPoint = (visitedNodes == prevVisitedNodes) || (visitedNodes == adjacencyMatrix.numCols); } - return result; + return new BfsDenseResult(result, iteration - 1, semiRing.add.id); } - public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int[] startNodes, int maxIterations) { + public BfsSparseResult computeSparse(DMatrixSparseCSC adjacencyMatrix, BfsVariation bfsVariation, int[] startNodes, int maxIterations) { // TODO: use transposed result matrix as startNodes.length << adjacencyMatrix.length // need to transpose result of VxM before combining DMatrixSparseCSC result = new DMatrixSparseCSC(startNodes.length, adjacencyMatrix.numCols); @@ -184,11 +184,73 @@ public static DMatrixSparseCSC computeSparse(DMatrixSparseCSC adjacencyMatrix, B isFixPoint = (visitedNodes == prevVisitedNodes) || (visitedNodes == adjacencyMatrix.numCols); } - return result; + return new BfsSparseResult(result, iteration - 1); } public enum BfsVariation { BOOLEAN, PARENTS, LEVEL } + + public interface BfsResult { + int iterations(); + int nodesVisited(); + } + + public class BfsSparseResult implements BfsResult { + private final DMatrixSparseCSC result; + private final int iterations; + + public BfsSparseResult(DMatrixSparseCSC result, int iterations) { + this.result = result; + this.iterations = iterations; + } + + @Override + public int iterations() { + return this.iterations; + } + + @Override + public int nodesVisited() { + return this.result.getNonZeroLength(); + } + + + public DMatrixSparseCSC result() { + return this.result; + } + } + + public class BfsDenseResult implements BfsResult { + private final double[] result; + private final double notFoundValue; + private final int iterations; + + public BfsDenseResult(double[] result, int iterations, double notFoundValue) { + this.result = result; + this.iterations = iterations; + this.notFoundValue = notFoundValue; + } + + @Override + public int iterations() { + return this.iterations; + } + + @Override + public int nodesVisited() { + int visited = 0; + + for (double v : result) { + if (v != notFoundValue) visited++; + } + + return visited; + } + + public double[] result() { + return this.result; + } + } } diff --git a/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java b/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java index e82c2a309..d87122fbe 100644 --- a/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java +++ b/main/ejml-dsparse/test/org/ejml/sparse/csc/graphAlgos/BfsTest_DSCC.java @@ -30,13 +30,14 @@ import java.util.Arrays; import java.util.stream.Stream; -import static org.ejml.sparse.csc.graphAlgos.BFS_DSCC.*; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.ejml.sparse.csc.graphAlgos.Bfs_DSCC.*; +import static org.junit.jupiter.api.Assertions.*; public class BfsTest_DSCC { DMatrixSparseCSC inputMatrix; + Bfs_DSCC bfs = new Bfs_DSCC(); + private static Stream bfsVariantSource() { return Stream.of( @@ -70,21 +71,34 @@ public void setUp() { public void testSparseVariations(BfsVariation variation, double[] expected) { int[] startNodes = {0}; int maxIterations = 20; - DMatrixSparseCSC result = computeSparse(inputMatrix, variation, startNodes, maxIterations); + Bfs_DSCC.BfsSparseResult result = bfs.computeSparse(inputMatrix, variation, startNodes, maxIterations); DMatrixRMaj expectedMatrix = new DMatrixRMaj(1, inputMatrix.numCols, true, expected); - EjmlUnitTests.assertEquals(expectedMatrix, result); + + assertEquals(result.iterations(), 3); + EjmlUnitTests.assertEquals(expectedMatrix, result.result()); } - // ! skipping BfsVariation.PARENTS for now @ParameterizedTest @MethodSource("bfsVariantSource") public void testDenseVariations(BfsVariation variation, double[] expected) { int startNode = 0; int maxIterations = 20; - double[] result = computeDense(inputMatrix, variation, startNode, maxIterations); + Bfs_DSCC.BfsDenseResult result = bfs.computeDense(inputMatrix, variation, startNode, maxIterations); + + assertEquals(result.iterations(), 3); + assertTrue(Arrays.equals(expected, result.result())); + } + + @Test + public void testEmptyResult() { + BfsResult denseIt = bfs.computeDense(new DMatrixSparseCSC(2, 2), BfsVariation.LEVEL, 0, 5); + + int[] startNodes = {0}; + BfsResult sparseIt = bfs.computeSparse(new DMatrixSparseCSC(2, 2), BfsVariation.LEVEL, startNodes, 5); - assertTrue(Arrays.equals(expected, result)); + assertEquals(denseIt.iterations(), sparseIt.iterations()); + assertEquals(denseIt.nodesVisited(), sparseIt.nodesVisited()); } -} \ No newline at end of file +} From 97384ba1336dbfee96723a384a1d758ece573842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Tue, 25 Aug 2020 13:40:09 +0200 Subject: [PATCH 48/48] Add `get` method to Bfs-result --- .../ejml/sparse/csc/graphAlgos/Bfs_DSCC.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java index bc7505d84..0cb1dafb9 100644 --- a/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java +++ b/main/ejml-dsparse/src/org/ejml/sparse/csc/graphAlgos/Bfs_DSCC.java @@ -61,7 +61,7 @@ public BfsDenseResult computeDense(DMatrixSparseCSC adjacencyMatrix, BfsVariatio double[] iterationResult = new double[adjacencyMatrix.numCols]; int visitedNodes = 1; - int prevVisitedNodes = -1; + int prevVisitedNodes; double[] inputVector = result.clone(); boolean isFixPoint = false; @@ -135,7 +135,7 @@ public BfsSparseResult computeSparse(DMatrixSparseCSC adjacencyMatrix, BfsVariat DGrowArray gx = new DGrowArray(); int visitedNodes = startNodes.length; - int prevVisitedNodes = -1; + int prevVisitedNodes; DSemiRing semiRing = getSemiRing(bfsVariation); @@ -184,7 +184,8 @@ public BfsSparseResult computeSparse(DMatrixSparseCSC adjacencyMatrix, BfsVariat isFixPoint = (visitedNodes == prevVisitedNodes) || (visitedNodes == adjacencyMatrix.numCols); } - return new BfsSparseResult(result, iteration - 1); + // expect the result to be a row vector / row vectors + return new BfsSparseResult(result, iteration - 1, semiRing.add.id); } @@ -195,15 +196,18 @@ public enum BfsVariation { public interface BfsResult { int iterations(); int nodesVisited(); + double get(int nodeId); } public class BfsSparseResult implements BfsResult { private final DMatrixSparseCSC result; + private final double notFoundValue; private final int iterations; - public BfsSparseResult(DMatrixSparseCSC result, int iterations) { + public BfsSparseResult(DMatrixSparseCSC result, int iterations, double fallBackValue) { this.result = result; this.iterations = iterations; + notFoundValue = fallBackValue; } @Override @@ -216,6 +220,10 @@ public int nodesVisited() { return this.result.getNonZeroLength(); } + @Override + public double get(int nodeId) { + return result.get(0, nodeId, notFoundValue); + } public DMatrixSparseCSC result() { return this.result; @@ -249,6 +257,11 @@ public int nodesVisited() { return visited; } + @Override + public double get(int nodeId) { + return result[nodeId]; + } + public double[] result() { return this.result; }