blob: f9746f329f9dd972dacc1d976a57d6e952e00f50 [file]
/*
* 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.functionobjects;
import org.apache.sysds.runtime.DMLRuntimeException;
import org.apache.sysds.runtime.matrix.data.CTableMap;
import org.apache.sysds.runtime.matrix.data.MatrixBlock;
import org.apache.sysds.runtime.matrix.data.MatrixIndexes;
import org.apache.sysds.runtime.matrix.data.Pair;
import org.apache.sysds.runtime.util.CommonThreadPool;
import org.apache.sysds.runtime.util.LongLongDoubleHashMap;
import org.apache.sysds.runtime.util.UtilFunctions;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
public class CTable extends ValueFunction
{
private static final long serialVersionUID = -5374880447194177236L;
private static CTable singleObj = null;
private CTable() {
// nothing to do here
}
public static CTable getCTableFnObject() {
if ( singleObj == null )
singleObj = new CTable();
return singleObj;
}
public void execute(double v1, double v2, double w, boolean ignoreZeros, CTableMap resultMap, MatrixBlock resultBlock) {
if( resultBlock != null )
execute(v1, v2, w, ignoreZeros, resultBlock);
else
execute(v1, v2, w, ignoreZeros, resultMap);
}
public void execute(double v1, double v2, double w, boolean ignoreZeros, CTableMap resultMap) {
// If any of the values are NaN (i.e., missing) then
// we skip this tuple, proceed to the next tuple
if ( Double.isNaN(v1) || Double.isNaN(v2) || Double.isNaN(w) ) {
return;
}
// safe casts to long for consistent behavior with indexing
long row = UtilFunctions.toLong( v1 );
long col = UtilFunctions.toLong( v2 );
// skip this entry as it does not fall within specified output dimensions
if( ignoreZeros && row == 0 && col == 0 ) {
return;
}
//check for incorrect ctable inputs
if( row <= 0 || col <= 0 ) {
throw new DMLRuntimeException("Erroneous input while computing the contingency table (one of the value <= zero): "+v1+" "+v2);
}
//hash group-by for core ctable computation
resultMap.aggregate(row, col, w);
}
public void execute(double v1, double v2, double w, boolean ignoreZeros, MatrixBlock ctableResult)
{
// If any of the values are NaN (i.e., missing) then
// we skip this tuple, proceed to the next tuple
if ( Double.isNaN(v1) || Double.isNaN(v2) || Double.isNaN(w) ) {
return;
}
// safe casts to long for consistent behavior with indexing
long row = UtilFunctions.toLong( v1 );
long col = UtilFunctions.toLong( v2 );
// skip this entry as it does not fall within specified output dimensions
if( ignoreZeros && row == 0 && col == 0 ) {
return;
}
//check for incorrect ctable inputs
if( row <= 0 || col <= 0 ) {
throw new DMLRuntimeException("Erroneous input while computing the contingency table (one of the value <= zero): "+v1+" "+v2);
}
// skip this entry as it does not fall within specified output dimensions
if( row > ctableResult.getNumRows() || col > ctableResult.getNumColumns() ) {
return;
}
//add value
ctableResult.set((int)row-1, (int)col-1,
ctableResult.get((int)row-1, (int)col-1) + w);
}
public int execute(int row, double v2, double w, int maxCol, int[] retIx, double[] retVals)
{
// If any of the values are NaN (i.e., missing) then
// we skip this tuple, proceed to the next tuple
if ( Double.isNaN(v2) || Double.isNaN(w) ) {
return maxCol;
}
// safe casts to long for consistent behavior with indexing
int col = UtilFunctions.toInt( v2 );
if( col <= 0 ) {
throw new DMLRuntimeException("Erroneous input while computing the contingency table (value <= zero): "+v2);
}
//set weight as value (expand is guaranteed to address different cells)
retIx[row - 1] = col - 1;
retVals[row - 1] = w;
//maintain max seen col
return Math.max(maxCol, col);
}
public Pair<MatrixIndexes,Double> execute( long row, double v2, double w )
{
// If any of the values are NaN (i.e., missing) then
// we skip this tuple, proceed to the next tuple
if ( Double.isNaN(v2) || Double.isNaN(w) )
return new Pair<>(new MatrixIndexes(-1,-1), w);
// safe casts to long for consistent behavior with indexing
long col = UtilFunctions.toLong( v2 );
if( col <= 0 )
throw new DMLRuntimeException("Erroneous input while computing the contingency table (value <= zero): "+v2);
return new Pair<>(new MatrixIndexes(row, col), w);
}
/* Multithreaded CTable (F = ctable(A,B,W))
* Divide the input vectors into equal-sized blocks and assign each block to a task.
* All tasks concurrently build their own CTableMaps.
* Cascade merge the partial maps.
* TODO: Support other cases
*/
public void execute(MatrixBlock in1, MatrixBlock in2, MatrixBlock w, CTableMap resultMap, int k) {
ExecutorService pool = CommonThreadPool.get(k);
ArrayList<CTableMap> partialMaps = new ArrayList<>();
try {
// Assign an equal-sized blocks to each task
List<Callable<Object>> tasks = new ArrayList<>();
int[] blockSizes = UtilFunctions.getBlockSizes(in1.getNumRows(), k);
// Each task builds a separate CTableMap in a lock-free manner
for(int startRow = 0, i = 0; i < blockSizes.length; startRow += blockSizes[i], i++)
tasks.add(getPartialCTableTask(in1, in2, w, startRow, blockSizes[i], partialMaps));
for(var task : pool.invokeAll(tasks))
task.get();
ArrayList<CTableMap> newPartialMaps = new ArrayList<>();
// Cascade-merge all the partial CTableMaps
while(partialMaps.size() > 1) {
newPartialMaps.clear();
tasks = new ArrayList<>();
int count;
// Each task merges 2 maps and returns the merged map
for (count=0; count+1<partialMaps.size(); count=count+2)
tasks.add(getMergePartialCTMapsTask(partialMaps.get(count),
partialMaps.get(count+1), newPartialMaps));
for(var task : pool.invokeAll(tasks))
task.get();
// Copy the remaining maps to be merged in the future iterations
if (count < partialMaps.size())
newPartialMaps.add(partialMaps.get(count));
partialMaps.clear();
partialMaps.addAll(newPartialMaps);
}
}
catch(Exception ex) {
throw new DMLRuntimeException(ex);
}
finally{
pool.shutdown();
}
// Deep copy the last merged map into the result map
var map = partialMaps.get(0);
Iterator<LongLongDoubleHashMap.ADoubleEntry> iter = map.getIterator();
while(iter.hasNext()) {
LongLongDoubleHashMap.ADoubleEntry e = iter.next();
resultMap.aggregate(e.getKey1(), e.getKey2(), e.value);
}
}
public Callable<Object> getPartialCTableTask(MatrixBlock in1, MatrixBlock in2, MatrixBlock w,
int startInd, int blockSize, ArrayList<CTableMap> pmaps) {
return new PartialCTableTask(in1, in2, w, startInd, blockSize, pmaps);
}
public Callable<Object> getMergePartialCTMapsTask(CTableMap map1, CTableMap map2, ArrayList<CTableMap> pmaps) {
return new MergePartialCTMaps(map1, map2, pmaps);
}
private static class PartialCTableTask implements Callable<Object> {
private final MatrixBlock _in1;
private final MatrixBlock _in2;
private final MatrixBlock _w;
private final int _startInd;
private final int _blockSize;
private final ArrayList<CTableMap> _partialCTmaps;
protected PartialCTableTask(MatrixBlock in1, MatrixBlock in2, MatrixBlock w,
int startRow, int blockSize, ArrayList<CTableMap> pmaps) {
_in1 = in1;
_in2 = in2;
_w = w;
_startInd = startRow;
_blockSize = blockSize;
_partialCTmaps = pmaps;
}
@Override public Object call() throws Exception {
CTable ctable = CTable.getCTableFnObject();
CTableMap ctmap = new CTableMap(LongLongDoubleHashMap.EntryType.INT);
int endInd = UtilFunctions.getEndIndex(_in1.getNumRows(), _startInd, _blockSize);
for( int i=_startInd; i<endInd; i++ )
{
double v1 = _in1.get(i, 0);
double v2 = _in2.get(i, 0);
double w = _w.get(i, 0);
ctable.execute(v1, v2, w, false, ctmap);
}
synchronized(_partialCTmaps) {
_partialCTmaps.add(ctmap);
}
return null;
}
}
private static class MergePartialCTMaps implements Callable<Object> {
private final CTableMap _map1;
private final CTableMap _map2;
private final ArrayList<CTableMap> _partialCTmaps;
protected MergePartialCTMaps(CTableMap map1, CTableMap map2, ArrayList<CTableMap> pmaps) {
_map1 = map1;
_map2 = map2;
_partialCTmaps = pmaps;
}
private void mergeToFinal(CTableMap map, CTableMap finalMap) {
Iterator<LongLongDoubleHashMap.ADoubleEntry> iter = map.getIterator();
while(iter.hasNext()) {
LongLongDoubleHashMap.ADoubleEntry e = iter.next();
finalMap.aggregate(e.getKey1(), e.getKey2(), e.value);
}
}
@Override public Object call() throws Exception {
CTableMap mergedMap = new CTableMap(LongLongDoubleHashMap.EntryType.INT);
mergeToFinal(_map1, mergedMap);
mergeToFinal(_map2, mergedMap);
synchronized(_partialCTmaps) {
_partialCTmaps.add(mergedMap);
return null;
}
}
}
}