This is an automated email from the ASF dual-hosted git repository.
mboehm7 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/systemds.git
The following commit(s) were added to refs/heads/main by this push:
new ebd3aed4de [SYSTEMDS-3915] Out-of-core ctable operations
ebd3aed4de is described below
commit ebd3aed4de3e3327f47cbcda2aa50f8f0c0d3c59
Author: Jessica Priebe <[email protected]>
AuthorDate: Sat Oct 25 16:12:28 2025 +0200
[SYSTEMDS-3915] Out-of-core ctable operations
Closes #2342.
---
.../runtime/instructions/OOCInstructionParser.java | 5 +-
.../instructions/ooc/CtableOOCInstruction.java | 204 +++++++++++++++++++++
.../runtime/instructions/ooc/OOCInstruction.java | 2 +-
.../sysds/test/functions/ooc/CTableTest.java | 139 ++++++++++++++
src/test/scripts/functions/ooc/CTableTest.R | 34 ++++
src/test/scripts/functions/ooc/CTableTest.dml | 27 +++
.../scripts/functions/ooc/WeightedCTableTest.R | 35 ++++
.../scripts/functions/ooc/WeightedCTableTest.dml | 28 +++
8 files changed, 472 insertions(+), 2 deletions(-)
diff --git
a/src/main/java/org/apache/sysds/runtime/instructions/OOCInstructionParser.java
b/src/main/java/org/apache/sysds/runtime/instructions/OOCInstructionParser.java
index 9c0f0f2e0f..4e9a92ecb7 100644
---
a/src/main/java/org/apache/sysds/runtime/instructions/OOCInstructionParser.java
+++
b/src/main/java/org/apache/sysds/runtime/instructions/OOCInstructionParser.java
@@ -26,6 +26,7 @@ import org.apache.sysds.runtime.DMLRuntimeException;
import org.apache.sysds.runtime.instructions.ooc.AggregateUnaryOOCInstruction;
import org.apache.sysds.runtime.instructions.ooc.BinaryOOCInstruction;
import org.apache.sysds.runtime.instructions.ooc.CentralMomentOOCInstruction;
+import org.apache.sysds.runtime.instructions.ooc.CtableOOCInstruction;
import org.apache.sysds.runtime.instructions.ooc.OOCInstruction;
import org.apache.sysds.runtime.instructions.ooc.ReblockOOCInstruction;
import org.apache.sysds.runtime.instructions.ooc.TSMMOOCInstruction;
@@ -72,7 +73,9 @@ public class OOCInstructionParser extends InstructionParser {
return TeeOOCInstruction.parseInstruction(str);
case CentralMoment:
return CentralMomentOOCInstruction.parseInstruction(str);
-
+ case Ctable:
+ return
CtableOOCInstruction.parseInstruction(str);
+
default:
throw new DMLRuntimeException("Invalid OOC
Instruction Type: " + ooctype);
}
diff --git
a/src/main/java/org/apache/sysds/runtime/instructions/ooc/CtableOOCInstruction.java
b/src/main/java/org/apache/sysds/runtime/instructions/ooc/CtableOOCInstruction.java
new file mode 100644
index 0000000000..c4c668ab6b
--- /dev/null
+++
b/src/main/java/org/apache/sysds/runtime/instructions/ooc/CtableOOCInstruction.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sysds.runtime.instructions.ooc;
+
+import java.util.HashMap;
+
+import org.apache.sysds.common.Types;
+import org.apache.sysds.lops.Ctable;
+import org.apache.sysds.runtime.DMLRuntimeException;
+import org.apache.sysds.runtime.controlprogram.caching.MatrixObject;
+import org.apache.sysds.runtime.controlprogram.context.ExecutionContext;
+import org.apache.sysds.runtime.controlprogram.parfor.LocalTaskQueue;
+import org.apache.sysds.runtime.instructions.Instruction;
+import org.apache.sysds.runtime.instructions.InstructionUtils;
+import org.apache.sysds.runtime.instructions.cp.CPOperand;
+import org.apache.sysds.runtime.instructions.spark.data.IndexedMatrixValue;
+import org.apache.sysds.runtime.matrix.data.CTableMap;
+import org.apache.sysds.runtime.matrix.data.MatrixBlock;
+import org.apache.sysds.runtime.matrix.operators.Operator;
+import org.apache.sysds.runtime.util.DataConverter;
+import org.apache.sysds.runtime.util.LongLongDoubleHashMap;
+
+public class CtableOOCInstruction extends ComputationOOCInstruction {
+ private final CPOperand _outDim1;
+ private final CPOperand _outDim2;
+ private final boolean _ignoreZeros;
+
+ protected CtableOOCInstruction(OOCType type, Operator op, CPOperand in1,
+ CPOperand in2, CPOperand in3, CPOperand out, CPOperand outDim1,
CPOperand outDim2,
+ boolean ignoreZeros, String opcode, String istr)
+ {
+ super(type, op, in1, in2, in3, out, opcode, istr);
+ _ignoreZeros = ignoreZeros;
+ _outDim1 = outDim1;
+ _outDim2 = outDim2;
+ }
+
+ public static CtableOOCInstruction parseInstruction(String str) {
+ String[] parts =
InstructionUtils.getInstructionPartsWithValueType(str);
+ InstructionUtils.checkNumFields(parts, 8);
+
+ String opcode = parts[0];
+ CPOperand in1 = new CPOperand(parts[1]);
+ CPOperand in2 = new CPOperand(parts[2]);
+ CPOperand in3 = new CPOperand(parts[3]);
+ CPOperand out = new CPOperand(parts[6]);
+
+ String[] dim1Fields =
parts[4].split(Instruction.LITERAL_PREFIX);
+ String[] dim2Fields =
parts[5].split(Instruction.LITERAL_PREFIX);
+ CPOperand outDim1 = new CPOperand(dim1Fields[0],
Types.ValueType.FP64,
+ Types.DataType.SCALAR,
Boolean.parseBoolean(dim1Fields[1]));
+ CPOperand outDim2 = new CPOperand(dim2Fields[0],
Types.ValueType.FP64,
+ Types.DataType.SCALAR,
Boolean.parseBoolean(dim2Fields[1]));
+
+ boolean ignoreZeros = Boolean.parseBoolean(parts[7]);
+
+ // does not require any op
+ return new CtableOOCInstruction(OOCType.Ctable, null, in1, in2,
in3, out, outDim1, outDim2, ignoreZeros, opcode, str);
+ }
+
+ @Override
+ public void processInstruction( ExecutionContext ec ) {
+
+ MatrixObject in1 = ec.getMatrixObject(input1); // stream
+ LocalTaskQueue<IndexedMatrixValue> qIn1 = in1.getStreamHandle();
+ IndexedMatrixValue tmp1 = null;
+
+ long outputDim1 = ec.getScalarInput(_outDim1).getLongValue();
+ long outputDim2 = ec.getScalarInput(_outDim2).getLongValue();
+
+ long cols = in1.getDataCharacteristics().getNumColBlocks();
+ CTableMap map = new
CTableMap(LongLongDoubleHashMap.EntryType.INT);
+
+ Ctable.OperationTypes ctableOp = findCtableOperation();
+ MatrixObject in2 = null, in3 = null;
+ LocalTaskQueue<IndexedMatrixValue> qIn2 = null, qIn3 = null;
+ double cst2 = 0, cst3 = 0;
+
+ // init vars based on ctableOp
+ if (ctableOp.hasSecondInput()){
+ in2 = ec.getMatrixObject(input2); // stream
+ qIn2 = in2.getStreamHandle();
+ } else
+ cst2 = ec.getScalarInput(input2).getDoubleValue();
+
+ if (ctableOp.hasThirdInput()){
+ in3 = ec.getMatrixObject(input3); // stream
+ qIn3 = in3.getStreamHandle();
+ } else
+ cst3 = ec.getScalarInput(input3).getDoubleValue();
+
+ HashMap<Long, MatrixBlock> blocksIn2 = new HashMap<>(),
blocksIn3 = new HashMap<>();
+ MatrixBlock block2, block3;
+
+ // only init result block if output dims known and dense
+ MatrixBlock result = null;
+ boolean outputDimsKnown = (outputDim1 != -1 && outputDim2 !=
-1);
+ if (outputDimsKnown){
+ long totalRows = in1.getDataCharacteristics().getRows();
+ long totalCols = in1.getDataCharacteristics().getCols();
+ boolean sparse =
MatrixBlock.evalSparseFormatInMemory(outputDim1, outputDim2,
totalRows*totalCols);
+ if(!sparse)
+ result = new MatrixBlock((int)outputDim1,
(int)outputDim2, false);
+ }
+
+ try {
+ while((tmp1 = qIn1.dequeueTask()) !=
LocalTaskQueue.NO_MORE_TASKS) {
+
+ MatrixBlock block1 = (MatrixBlock)
tmp1.getValue();
+ long r = tmp1.getIndexes().getRowIndex();
+ long c = tmp1.getIndexes().getColumnIndex();
+ long key = (r-1) * cols + (c-1);
+
+ switch(ctableOp) {
+ case CTABLE_TRANSFORM:
+ // ctable(A,B,W)
+ block2 = getOrDequeueBlock(key,
cols, blocksIn2, qIn2);
+ block3 = getOrDequeueBlock(key,
cols, blocksIn3, qIn3);
+ block1.ctableOperations(_optr,
block2, block3, map, result);
+ break;
+ case CTABLE_TRANSFORM_SCALAR_WEIGHT:
+ // ctable(A,B) or ctable(A,B,1)
+ block2 = getOrDequeueBlock(key,
cols, blocksIn2, qIn2);
+ block1.ctableOperations(_optr,
block2, cst3, _ignoreZeros, map, result);
+ break;
+ case CTABLE_TRANSFORM_HISTOGRAM:
+ // ctable(A,1) or ctable(A,1,1)
+ block1.ctableOperations(_optr,
cst2, cst3, map, result);
+ break;
+ case
CTABLE_TRANSFORM_WEIGHTED_HISTOGRAM:
+ // ctable(A,1,W)
+ block3 = getOrDequeueBlock(key,
cols, blocksIn3, qIn3);
+ block1.ctableOperations(_optr,
cst2, block3, map, result);
+ break;
+
+ default:
+ throw new
DMLRuntimeException("Encountered an invalid OOC ctable operation "
+ + "("+ctableOp+") while
executing instruction: " + this);
+ }
+ }
+ if (result == null){
+ if(outputDimsKnown)
+ result =
DataConverter.convertToMatrixBlock(map, (int)outputDim1, (int)outputDim2);
+ else
+ result =
DataConverter.convertToMatrixBlock(map);
+ }
+ else
+ result.examSparsity();
+
+ ec.setMatrixOutput(output.getName(), result);
+ }
+ catch(Exception ex) {
+ throw new DMLRuntimeException(ex);
+ }
+ }
+
+ private MatrixBlock getOrDequeueBlock(long key, long cols,
HashMap<Long, MatrixBlock> blocks,
+ LocalTaskQueue<IndexedMatrixValue> queue) throws
InterruptedException
+ {
+ MatrixBlock block = blocks.get(key);
+ if (block == null) {
+ IndexedMatrixValue tmp;
+ // corresponding block still in queue, dequeue until
found
+ while ((tmp = queue.dequeueTask()) !=
LocalTaskQueue.NO_MORE_TASKS) {
+ block = (MatrixBlock) tmp.getValue();
+ long r = tmp.getIndexes().getRowIndex();
+ long c = tmp.getIndexes().getColumnIndex();
+ long tmpKey = (r-1) * cols + (c-1);
+ // found corresponding block
+ if (tmpKey == key) break;
+ // store all dequeued blocks in cache that we
don't need yet
+ blocks.put(tmpKey, block);
+ }
+ }
+ else
+ blocks.remove(key); // needed only once
+
+ return block;
+ }
+
+ private Ctable.OperationTypes findCtableOperation() {
+ Types.DataType dt1 = input1.getDataType();
+ Types.DataType dt2 = input2.getDataType();
+ Types.DataType dt3 = input3.getDataType();
+ return Ctable.findCtableOperationByInputDataTypes(dt1, dt2,
dt3);
+ }
+}
diff --git
a/src/main/java/org/apache/sysds/runtime/instructions/ooc/OOCInstruction.java
b/src/main/java/org/apache/sysds/runtime/instructions/ooc/OOCInstruction.java
index 5b1c766661..d55d1ee594 100644
---
a/src/main/java/org/apache/sysds/runtime/instructions/ooc/OOCInstruction.java
+++
b/src/main/java/org/apache/sysds/runtime/instructions/ooc/OOCInstruction.java
@@ -33,7 +33,7 @@ public abstract class OOCInstruction extends Instruction {
protected static final Log LOG =
LogFactory.getLog(OOCInstruction.class.getName());
public enum OOCType {
- Reblock, Tee, Binary, Unary, AggregateUnary, AggregateBinary,
MAPMM, MMTSJ, Reorg, CM
+ Reblock, Tee, Binary, Unary, AggregateUnary, AggregateBinary,
MAPMM, MMTSJ, Reorg, CM, Ctable
}
protected final OOCInstruction.OOCType _ooctype;
diff --git a/src/test/java/org/apache/sysds/test/functions/ooc/CTableTest.java
b/src/test/java/org/apache/sysds/test/functions/ooc/CTableTest.java
new file mode 100644
index 0000000000..cda213ee09
--- /dev/null
+++ b/src/test/java/org/apache/sysds/test/functions/ooc/CTableTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sysds.test.functions.ooc;
+
+import org.apache.sysds.common.Opcodes;
+import org.apache.sysds.common.Types;
+import org.apache.sysds.runtime.instructions.Instruction;
+import org.apache.sysds.runtime.io.MatrixWriter;
+import org.apache.sysds.runtime.io.MatrixWriterFactory;
+import org.apache.sysds.runtime.matrix.data.MatrixValue;
+import org.apache.sysds.runtime.meta.MatrixCharacteristics;
+import org.apache.sysds.runtime.util.DataConverter;
+import org.apache.sysds.runtime.util.HDFSTool;
+import org.apache.sysds.test.AutomatedTestBase;
+import org.apache.sysds.test.TestConfiguration;
+import org.apache.sysds.test.TestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+
+public class CTableTest extends AutomatedTestBase{
+ private static final String TEST_NAME1 = "CTableTest";
+ private static final String TEST_NAME2 = "WeightedCTableTest";
+ private static final String TEST_DIR = "functions/ooc/";
+ private static final String TEST_CLASS_DIR = TEST_DIR +
CTableTest.class.getSimpleName() + "/";
+
+ private static final String INPUT_NAME1 = "v";
+ private static final String INPUT_NAME2 = "w";
+ private static final String INPUT_NAME3 = "weights";
+ private static final String OUTPUT_NAME = "res";
+
+ private final static double eps = 1e-10;
+
+ @Override
+ public void setUp() {
+ TestUtils.clearAssertionInformation();
+ addTestConfiguration(TEST_NAME1, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME1));
+ addTestConfiguration(TEST_NAME2, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME2));
+ }
+
+ @Test
+ public void testCTableSimple(){ testCTable(1372, 1012, 5, 5, false);}
+
+ @Test
+ public void testCTableValueSetDifferencesNonEmpty(){ testCTable(2000,
37, 4995, 5, false);}
+
+ @Test
+ public void testWeightedCTableSimple(){ testCTable(1372, 1012, 5, 5,
true);}
+
+ @Test
+ public void testWeightedCTableValueSetDifferencesNonEmpty(){
testCTable(2000, 37, 4995, 5, true);}
+
+
+ public void testCTable(int rows, int cols, int maxValV, int maxValW,
boolean isWeighted)
+ {
+ Types.ExecMode platformOld = rtplatform;
+ rtplatform = Types.ExecMode.SINGLE_NODE;
+
+ try {
+ String TEST_NAME = isWeighted? TEST_NAME2:TEST_NAME1;
+
+ getAndLoadTestConfiguration(TEST_NAME);
+ String HOME = SCRIPT_DIR + TEST_DIR;
+ fullDMLScriptName = HOME + TEST_NAME + ".dml";
+ if (isWeighted)
+ programArgs = new String[] {"-explain",
"-stats", "-ooc", "-args", input(INPUT_NAME1), input(INPUT_NAME2),
input(INPUT_NAME3), output(OUTPUT_NAME)};
+ else
+ programArgs = new String[] {"-explain",
"-stats", "-ooc", "-args", input(INPUT_NAME1), input(INPUT_NAME2),
output(OUTPUT_NAME)};
+
+ fullRScriptName = HOME + TEST_NAME + ".R";
+ rCmd = "Rscript" + " " + fullRScriptName + " " +
inputDir() + " " + expectedDir();
+
+ // values <=0 invalid
+ double[][] v = TestUtils.floor(getRandomMatrix(rows,
cols, 1, maxValV, 1.0, 7));
+ double[][] w = TestUtils.floor(getRandomMatrix(rows,
cols, 1, maxValW, 1.0, 13));
+ double[][] weights = null;
+
+ MatrixWriter writer =
MatrixWriterFactory.createMatrixWriter(Types.FileFormat.BINARY);
+
writer.writeMatrixToHDFS(DataConverter.convertToMatrixBlock(v),
input(INPUT_NAME1), rows, cols, 1000, rows*cols);
+
writer.writeMatrixToHDFS(DataConverter.convertToMatrixBlock(w),
input(INPUT_NAME2), rows, cols, 1000, rows*cols);
+
+ HDFSTool.writeMetaDataFile(input(INPUT_NAME1+".mtd"),
Types.ValueType.FP64, new MatrixCharacteristics(rows,cols,1000,rows*cols),
Types.FileFormat.BINARY);
+ HDFSTool.writeMetaDataFile(input(INPUT_NAME2+".mtd"),
Types.ValueType.FP64, new MatrixCharacteristics(rows,cols,1000,rows*cols),
Types.FileFormat.BINARY);
+
+ // for RScript
+ writeInputMatrixWithMTD("vR", v, true);
+ writeInputMatrixWithMTD("wR", w, true);
+
+ if (isWeighted) {
+ weights = TestUtils.floor(getRandomMatrix(rows,
cols, 1, maxValW, 1.0, 17));
+
writer.writeMatrixToHDFS(DataConverter.convertToMatrixBlock(weights),
+ input(INPUT_NAME3), rows, cols, 1000,
rows * cols);
+ HDFSTool.writeMetaDataFile(input(INPUT_NAME3 +
".mtd"), Types.ValueType.FP64,
+ new MatrixCharacteristics(rows, cols,
1000, rows * cols), Types.FileFormat.BINARY);
+ writeInputMatrixWithMTD("weightsR", weights,
true);
+ }
+
+ runTest(true, false, null, -1);
+ runRScript(true);
+
+ // compare matrices
+ HashMap<MatrixValue.CellIndex, Double> rfile =
readRMatrixFromExpectedDir("resR");
+
+ double[][] rRes =
TestUtils.convertHashMapToDoubleArray(rfile);
+ double[][] dmlRes = DataConverter.convertToDoubleMatrix(
+
DataConverter.readMatrixFromHDFS(output(OUTPUT_NAME),
+ Types.FileFormat.BINARY, rRes.length,
rRes[0].length, 1000, 1000));
+ TestUtils.compareMatrices(rRes, dmlRes, eps);
+
+ String prefix = Instruction.OOC_INST_PREFIX;
+ Assert.assertTrue("OOC wasn't used for RBLK",
+ heavyHittersContainsString(prefix +
Opcodes.RBLK));
+ }
+ catch(Exception ex) {
+ Assert.fail(ex.getMessage());
+ }
+ finally {
+ resetExecMode(platformOld);
+ }
+ }
+}
diff --git a/src/test/scripts/functions/ooc/CTableTest.R
b/src/test/scripts/functions/ooc/CTableTest.R
new file mode 100644
index 0000000000..a2b76ea0d2
--- /dev/null
+++ b/src/test/scripts/functions/ooc/CTableTest.R
@@ -0,0 +1,34 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+#-------------------------------------------------------------
+
+
+args <- commandArgs(TRUE)
+options(digits=22)
+
+library("Matrix")
+
+v <- as.matrix(readMM(paste(args[1], "vR.mtx", sep="")))
+w <- as.matrix(readMM(paste(args[1], "wR.mtx", sep="")))
+
+res = table (v, w);
+res = as.matrix(as.data.frame.matrix(res));
+
+writeMM(as(res, "CsparseMatrix"), paste(args[2], "resR", sep=""));
diff --git a/src/test/scripts/functions/ooc/CTableTest.dml
b/src/test/scripts/functions/ooc/CTableTest.dml
new file mode 100644
index 0000000000..1d8ff90be2
--- /dev/null
+++ b/src/test/scripts/functions/ooc/CTableTest.dml
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+#-------------------------------------------------------------
+
+
+v = read($1);
+w = read($2);
+res = table (v, w);
+
+write(res, $3, format="binary");
diff --git a/src/test/scripts/functions/ooc/WeightedCTableTest.R
b/src/test/scripts/functions/ooc/WeightedCTableTest.R
new file mode 100644
index 0000000000..5e3a3f698d
--- /dev/null
+++ b/src/test/scripts/functions/ooc/WeightedCTableTest.R
@@ -0,0 +1,35 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+#-------------------------------------------------------------
+
+
+args <- commandArgs(TRUE)
+options(digits=22)
+
+library("Matrix")
+
+v <- as.vector(readMM(paste(args[1], "vR.mtx", sep="")))
+w <- as.vector(readMM(paste(args[1], "wR.mtx", sep="")))
+weights <- as.vector(readMM(paste(args[1], "weightsR.mtx", sep="")))
+
+res = xtabs(weights ~ v + w)
+res = as.matrix(as.data.frame.matrix(res));
+
+writeMM(as(res, "CsparseMatrix"), paste(args[2], "resR", sep=""));
diff --git a/src/test/scripts/functions/ooc/WeightedCTableTest.dml
b/src/test/scripts/functions/ooc/WeightedCTableTest.dml
new file mode 100644
index 0000000000..5af9d34d4e
--- /dev/null
+++ b/src/test/scripts/functions/ooc/WeightedCTableTest.dml
@@ -0,0 +1,28 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+#-------------------------------------------------------------
+
+
+v = read($1);
+w = read($2);
+weights = read($3);
+res = table (v, w, weights);
+
+write(res, $4, format="binary");