/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.das2.qds.ops; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import org.das2.datum.LoggerManager; import org.das2.datum.UnitsConverter; import org.das2.qds.DataSetUtil; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.QDataSet; import org.das2.qds.QubeDataSetIterator; import org.das2.qds.SemanticOps; import org.das2.qds.WritableDataSet; import static org.das2.qds.ops.Ops.equalProperties; /** * * @author jbf */ public class OpsParl { private static final Logger logger= LoggerManager.getLogger("qdataset.ops"); private static final String CLASSNAME = OpsParl.class.getCanonicalName(); /** * this class cannot be instantiated. */ private OpsParl() { } /** * BinaryOps are operations such as add, pow, atan2 */ public interface BinaryOp { double op(double d1, double d2); } /** * these are properties that are propagated through operations. */ private static final String[] _dependProperties= new String[] { QDataSet.DEPEND_0, QDataSet.DEPEND_1, QDataSet.DEPEND_2, QDataSet.DEPEND_3, QDataSet.BINS_0, QDataSet.BINS_1, }; /** * apply the binary operator element-for-element of the two datasets, minding * dataset geometry, fill values, etc. The two datasets are coerced to * compatible geometry, if possible (e.g.Temperature[Time]+2deg), using * CoerceUtil.coerce. Structural metadata such as DEPEND_0 are preserved * where this is reasonable, and dimensional metadata such as UNITS are * dropped. * * @param ds1 the first argument * @param ds2 the second argument * @param op binary operation for each pair of elements * @return the result with the same geometry as the pair. */ public static MutablePropertyDataSet applyBinaryOp( QDataSet ds1, QDataSet ds2, BinaryOp op ) { //TODO: handle JOIN from RPWS group, which is not a QUBE... if ( ds1.rank()==ds2.rank() && ds1.rank()>0 ) { if ( ds1.length()!=ds2.length() ) { throw new IllegalArgumentException("binary operation on datasets of different lengths: "+ ds1 + " " + ds2 ); } } QDataSet[] operands= new QDataSet[2]; // if ( checkComplexArgument(ds1) || checkComplexArgument(ds2) ) { // if ( ds1.rank()!=ds2.rank() ) { // throw new IllegalArgumentException("complex numbers argument requires that rank must be equal."); // } // } // WritableDataSet result = CoerceUtil.coerce( ds1, ds2, true, operands ); QubeDataSetIterator it1 = new QubeDataSetIterator( operands[0] ); QubeDataSetIterator it2 = new QubeDataSetIterator( operands[1] ); QDataSet w1= DataSetUtil.weightsDataSet(operands[0]); QDataSet w2= DataSetUtil.weightsDataSet(operands[1]); double fill= -1e38; while (it1.hasNext()) { it1.next(); it2.next(); double d1 = it1.getValue(operands[0]); double d2 = it2.getValue(operands[1]); double w= it1.getValue(w1) * it2.getValue(w2); it1.putValue(result, w==0 ? fill : op.op(d1, d2)); } Map m1 = DataSetUtil.getProperties( operands[0], _dependProperties, null ); Map m2 = DataSetUtil.getProperties( operands[1], _dependProperties, null ); boolean resultIsQube= Boolean.TRUE.equals( m1.get(QDataSet.QUBE) ) || Boolean.TRUE.equals( m2.get(QDataSet.QUBE) ); if ( m1.size()==1 ) m1.remove( QDataSet.QUBE ); // kludge: CoerceUtil.coerce would copy over a QUBE property, so just remove this. if ( m2.size()==1 ) m2.remove( QDataSet.QUBE ); if ( ( m2.isEmpty() && !m1.isEmpty() ) || ds2.rank()==0 ) { m2.put( QDataSet.DEPEND_0, m1.get(QDataSet.DEPEND_0 ) ); m2.put( QDataSet.DEPEND_1, m1.get(QDataSet.DEPEND_1 ) ); m2.put( QDataSet.DEPEND_2, m1.get(QDataSet.DEPEND_2 ) ); m2.put( QDataSet.DEPEND_3, m1.get(QDataSet.DEPEND_3 ) ); m2.put( QDataSet.CONTEXT_0, m1.get(QDataSet.CONTEXT_0) ); m2.put( QDataSet.BINS_0, m1.get(QDataSet.BINS_0 ) ); m2.put( QDataSet.BINS_1, m1.get(QDataSet.BINS_1 ) ); } if ( ( m1.isEmpty() && !m2.isEmpty() ) || ds1.rank()==0 ) { m1.put( QDataSet.DEPEND_0, m2.get(QDataSet.DEPEND_0 ) ); m1.put( QDataSet.DEPEND_1, m2.get(QDataSet.DEPEND_1 ) ); m1.put( QDataSet.DEPEND_2, m2.get(QDataSet.DEPEND_2 ) ); m1.put( QDataSet.DEPEND_3, m2.get(QDataSet.DEPEND_3 ) ); m1.put( QDataSet.CONTEXT_0, m2.get(QDataSet.CONTEXT_0) ); m1.put( QDataSet.BINS_0, m2.get(QDataSet.BINS_0 ) ); m1.put( QDataSet.BINS_1, m2.get(QDataSet.BINS_1 ) ); } Map m3 = equalProperties(m1, m2); if ( resultIsQube ) { m3.put( QDataSet.QUBE, Boolean.TRUE ); } String[] ss= DataSetUtil.dimensionProperties(); for ( String s: ss ) { m3.remove( s ); } // because this contains FILL_VALUE, etc that are no longer correct. // My guess is that anything with a BUNDLE_1 property shouldn't have // been passed into this routine anyway. m3.remove( QDataSet.BUNDLE_1 ); DataSetUtil.putProperties(m3, result); result.putProperty( QDataSet.FILL_VALUE, fill ); return result; } /** * element-wise equality test. 1.0 is returned where the two datasets are * equal. Fill is returned where either measurement is invalid. * @param ds1 rank n dataset * @param ds2 rank m dataset with compatible geometry. * @return rank n or m dataset. */ public static QDataSet eq(QDataSet ds1, QDataSet ds2) { final UnitsConverter uc= SemanticOps.getLooseUnitsConverter( ds1, ds2 ); return applyBinaryOp(ds1, ds2, new BinaryOp() { @Override public double op(double d1, double d2) { return uc.convert(d1) == d2 ? 1.0 : 0.0; } }); } /** * apply the binary operator element-for-element of the two datasets, minding * dataset geometry, fill values, etc. The two datasets are coerced to * compatible geometry, if possible (e.g.Temperature[Time]+2deg), using * CoerceUtil.coerce. Structural metadata such as DEPEND_0 are preserved * where this is reasonable, and dimensional metadata such as UNITS are * dropped. * * This implementation avoids the use of DataSetIterators, which have been * shown to be slow. (But it's not known why.) * * @param ds1 the first argument * @param ds2 the second argument * @param op binary operation for each pair of elements * @return the result with the same geometry as the pair. */ public static MutablePropertyDataSet applyBinaryOpNoIter( QDataSet ds1, QDataSet ds2, BinaryOp op ) { //TODO: handle JOIN from RPWS group, which is not a QUBE... if ( ds1.rank()==ds2.rank() && ds1.rank()>0 ) { if ( ds1.length()!=ds2.length() ) { throw new IllegalArgumentException("binary operation on datasets of different lengths: "+ ds1 + " " + ds2 ); } } QDataSet[] operands= new QDataSet[2]; // if ( checkComplexArgument(ds1) || checkComplexArgument(ds2) ) { // if ( ds1.rank()!=ds2.rank() ) { // throw new IllegalArgumentException("complex numbers argument requires that rank must be equal."); // } // } // WritableDataSet result = CoerceUtil.coerce( ds1, ds2, true, operands ); QDataSet w1= DataSetUtil.weightsDataSet(operands[0]); QDataSet w2= DataSetUtil.weightsDataSet(operands[1]); double fill= -1e38; if ( w1.rank()==1 ) { int n= w1.length(); QDataSet op1= operands[0]; QDataSet op2= operands[1]; for ( int i=0; i m1 = DataSetUtil.getProperties( operands[0], _dependProperties, null ); Map m2 = DataSetUtil.getProperties( operands[1], _dependProperties, null ); boolean resultIsQube= Boolean.TRUE.equals( m1.get(QDataSet.QUBE) ) || Boolean.TRUE.equals( m2.get(QDataSet.QUBE) ); if ( m1.size()==1 ) m1.remove( QDataSet.QUBE ); // kludge: CoerceUtil.coerce would copy over a QUBE property, so just remove this. if ( m2.size()==1 ) m2.remove( QDataSet.QUBE ); if ( ( m2.isEmpty() && !m1.isEmpty() ) || ds2.rank()==0 ) { m2.put( QDataSet.DEPEND_0, m1.get(QDataSet.DEPEND_0 ) ); m2.put( QDataSet.DEPEND_1, m1.get(QDataSet.DEPEND_1 ) ); m2.put( QDataSet.DEPEND_2, m1.get(QDataSet.DEPEND_2 ) ); m2.put( QDataSet.DEPEND_3, m1.get(QDataSet.DEPEND_3 ) ); m2.put( QDataSet.CONTEXT_0, m1.get(QDataSet.CONTEXT_0) ); m2.put( QDataSet.BINS_0, m1.get(QDataSet.BINS_0 ) ); m2.put( QDataSet.BINS_1, m1.get(QDataSet.BINS_1 ) ); } if ( ( m1.isEmpty() && !m2.isEmpty() ) || ds1.rank()==0 ) { m1.put( QDataSet.DEPEND_0, m2.get(QDataSet.DEPEND_0 ) ); m1.put( QDataSet.DEPEND_1, m2.get(QDataSet.DEPEND_1 ) ); m1.put( QDataSet.DEPEND_2, m2.get(QDataSet.DEPEND_2 ) ); m1.put( QDataSet.DEPEND_3, m2.get(QDataSet.DEPEND_3 ) ); m1.put( QDataSet.CONTEXT_0, m2.get(QDataSet.CONTEXT_0) ); m1.put( QDataSet.BINS_0, m2.get(QDataSet.BINS_0 ) ); m1.put( QDataSet.BINS_1, m2.get(QDataSet.BINS_1 ) ); } Map m3 = equalProperties(m1, m2); if ( resultIsQube ) { m3.put( QDataSet.QUBE, Boolean.TRUE ); } String[] ss= DataSetUtil.dimensionProperties(); for ( String s: ss ) { m3.remove( s ); } // because this contains FILL_VALUE, etc that are no longer correct. // My guess is that anything with a BUNDLE_1 property shouldn't have // been passed into this routine anyway. m3.remove( QDataSet.BUNDLE_1 ); DataSetUtil.putProperties(m3, result); result.putProperty( QDataSet.FILL_VALUE, fill ); return result; } /** * element-wise equality test. 1.0 is returned where the two datasets are * equal. Fill is returned where either measurement is invalid. * @param ds1 rank n dataset * @param ds2 rank m dataset with compatible geometry. * @return rank n or m dataset. */ public static QDataSet eq_noiter(QDataSet ds1, QDataSet ds2) { final UnitsConverter uc= SemanticOps.getLooseUnitsConverter( ds1, ds2 ); return applyBinaryOpNoIter(ds1, ds2, new BinaryOp() { @Override public double op(double d1, double d2) { return uc.convert(d1) == d2 ? 1.0 : 0.0; } }); } /** * apply the binary operator element-for-element of the two datasets, minding * dataset geometry, fill values, etc. The two datasets are coerced to * compatible geometry, if possible (e.g.Temperature[Time]+2deg), using * CoerceUtil.coerce. Structural metadata such as DEPEND_0 are preserved * where this is reasonable, and dimensional metadata such as UNITS are * dropped. * * This implementation runs the trivially parallelizable task on separate * threads. * * @param ds1 the first argument * @param ds2 the second argument * @param op binary operation for each pair of elements * @return the result with the same geometry as the pair. */ public static MutablePropertyDataSet applyBinaryOpParl( QDataSet ds1, QDataSet ds2, final BinaryOp op ) { //TODO: handle JOIN from RPWS group, which is not a QUBE... if ( ds1.rank()==ds2.rank() && ds1.rank()>0 ) { if ( ds1.length()!=ds2.length() ) { throw new IllegalArgumentException("binary operation on datasets of different lengths: "+ ds1 + " " + ds2 ); } } QDataSet[] operands= new QDataSet[2]; // if ( checkComplexArgument(ds1) || checkComplexArgument(ds2) ) { // if ( ds1.rank()!=ds2.rank() ) { // throw new IllegalArgumentException("complex numbers argument requires that rank must be equal."); // } // } // final WritableDataSet result = CoerceUtil.coerce( ds1, ds2, true, operands ); final QDataSet w1= DataSetUtil.weightsDataSet(operands[0]); final QDataSet w2= DataSetUtil.weightsDataSet(operands[1]); final double fill= -1e38; if ( w1.rank()==1 ) { final int nthread= 4; final int n= w1.length(); final QDataSet op1= operands[0]; final QDataSet op2= operands[1]; List> callables= new ArrayList<>(4); for ( int i=0; i() { @Override public Object call() { for ( int i=istart; i> tasks= callables; List> futures; try { futures = executor.invokeAll(tasks); } catch (InterruptedException ex) { throw new RuntimeException(ex); } while ( true ) { boolean allDone= true; for ( Future f: futures ) { if ( !f.isDone() ) { allDone= false; break; } } if ( allDone ) break; } } else { QubeDataSetIterator it1 = new QubeDataSetIterator( operands[0] ); QubeDataSetIterator it2 = new QubeDataSetIterator( operands[1] ); while (it1.hasNext()) { it1.next(); it2.next(); double d1 = it1.getValue(operands[0]); double d2 = it2.getValue(operands[1]); double w= it1.getValue(w1) * it2.getValue(w2); it1.putValue(result, w==0 ? fill : op.op(d1, d2)); } } Map m1 = DataSetUtil.getProperties( operands[0], _dependProperties, null ); Map m2 = DataSetUtil.getProperties( operands[1], _dependProperties, null ); boolean resultIsQube= Boolean.TRUE.equals( m1.get(QDataSet.QUBE) ) || Boolean.TRUE.equals( m2.get(QDataSet.QUBE) ); if ( m1.size()==1 ) m1.remove( QDataSet.QUBE ); // kludge: CoerceUtil.coerce would copy over a QUBE property, so just remove this. if ( m2.size()==1 ) m2.remove( QDataSet.QUBE ); if ( ( m2.isEmpty() && !m1.isEmpty() ) || ds2.rank()==0 ) { m2.put( QDataSet.DEPEND_0, m1.get(QDataSet.DEPEND_0 ) ); m2.put( QDataSet.DEPEND_1, m1.get(QDataSet.DEPEND_1 ) ); m2.put( QDataSet.DEPEND_2, m1.get(QDataSet.DEPEND_2 ) ); m2.put( QDataSet.DEPEND_3, m1.get(QDataSet.DEPEND_3 ) ); m2.put( QDataSet.CONTEXT_0, m1.get(QDataSet.CONTEXT_0) ); m2.put( QDataSet.BINS_0, m1.get(QDataSet.BINS_0 ) ); m2.put( QDataSet.BINS_1, m1.get(QDataSet.BINS_1 ) ); } if ( ( m1.isEmpty() && !m2.isEmpty() ) || ds1.rank()==0 ) { m1.put( QDataSet.DEPEND_0, m2.get(QDataSet.DEPEND_0 ) ); m1.put( QDataSet.DEPEND_1, m2.get(QDataSet.DEPEND_1 ) ); m1.put( QDataSet.DEPEND_2, m2.get(QDataSet.DEPEND_2 ) ); m1.put( QDataSet.DEPEND_3, m2.get(QDataSet.DEPEND_3 ) ); m1.put( QDataSet.CONTEXT_0, m2.get(QDataSet.CONTEXT_0) ); m1.put( QDataSet.BINS_0, m2.get(QDataSet.BINS_0 ) ); m1.put( QDataSet.BINS_1, m2.get(QDataSet.BINS_1 ) ); } Map m3 = equalProperties(m1, m2); if ( resultIsQube ) { m3.put( QDataSet.QUBE, Boolean.TRUE ); } String[] ss= DataSetUtil.dimensionProperties(); for ( String s: ss ) { m3.remove( s ); } // because this contains FILL_VALUE, etc that are no longer correct. // My guess is that anything with a BUNDLE_1 property shouldn't have // been passed into this routine anyway. m3.remove( QDataSet.BUNDLE_1 ); DataSetUtil.putProperties(m3, result); result.putProperty( QDataSet.FILL_VALUE, fill ); return result; } /** * element-wise equality test. 1.0 is returned where the two datasets are * equal. Fill is returned where either measurement is invalid. * @param ds1 rank n dataset * @param ds2 rank m dataset with compatible geometry. * @return rank n or m dataset. */ public static QDataSet eq_parl(QDataSet ds1, QDataSet ds2) { final UnitsConverter uc= SemanticOps.getLooseUnitsConverter( ds1, ds2 ); return applyBinaryOpParl(ds1, ds2, new BinaryOp() { @Override public double op(double d1, double d2) { return uc.convert(d1) == d2 ? 1.0 : 0.0; } }); } }