/* * DDataSet.java * * Created on April 24, 2007, 11:08 PM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package org.das2.qds; import java.lang.reflect.Array; import java.util.Map; import org.das2.datum.Units; import org.das2.qds.ops.CoerceUtil; import org.das2.qds.ops.Ops; /** * rank 0,1,2,3 or 4 dataset backed by double array (8 byte real numbers). * * @author jbf */ public final class DDataSet extends ArrayDataSet { //static long t0= System.currentTimeMillis(); //public long t= System.currentTimeMillis() - t0; double[] back; private static final boolean RANGE_CHECK = "true".equals( System.getProperty("rangeChecking","true") ); public static final String version="20150219"; /** * create a rank 1 dataset backed by array of doubles. * @return rank 0 dataset backed by double. */ public static DDataSet createRank0() { return new DDataSet(0, 1, 1, 1, 1); } /** * create a rank 1 dataset backed by array of doubles. * @param len0 length of the dimension * @return rank 1 qube dataset of backed by array of doubles. */ public static DDataSet createRank1(int len0) { return new DDataSet(1, len0, 1, 1, 1); } /** * create a rank 2 qube dataset backed by array of doubles. * @param len0 length of the dimension * @param len1 length of the dimension * @return rank 2 qube dataset of backed by array of doubles. */ public static DDataSet createRank2(int len0, int len1) { return new DDataSet(2, len0, len1, 1, 1); } /** * create a rank 3 qube dataset backed by array of doubles. * @param len0 length of the dimension * @param len1 length of the dimension * @param len2 length of the dimension * @return rank 3 qube dataset of backed by array of doubles. */ public static DDataSet createRank3(int len0, int len1, int len2) { return new DDataSet(3, len0, len1, len2, 1); } /** * create a rank 4 qube dataset backed by array of doubles. * @param len0 length of the dimension * @param len1 length of the dimension * @param len2 length of the dimension * @param len3 length of the dimension * @return rank 4 qube dataset of backed by array of doubles. */ public static DDataSet createRank4(int len0, int len1, int len2, int len3) { return new DDataSet(4, len0, len1, len2, len3); } /** * Makes an array from array of dimension sizes. The result will have * rank qube.length(). * @param qube array specifying the rank and size of each dimension * @return the array as a QDataSet */ public static DDataSet create(int[] qube) { switch (qube.length) { case 0: return new DDataSet( 0, 1, 1, 1, 1 ); case 1: return DDataSet.createRank1(qube[0]); case 2: return DDataSet.createRank2(qube[0], qube[1]); case 3: return DDataSet.createRank3(qube[0], qube[1], qube[2]); case 4: return DDataSet.createRank4(qube[0], qube[1], qube[2], qube[3]); default: throw new IllegalArgumentException("bad qube"); } } /** * convenient method for creating DatumRanges bins datasets. * @param min the minumum value * @param max the maximum value * @param u the ratiometric or time location units * @return the rank1 bins dataset */ public static DDataSet createRank1Bins( double min, double max, Units u ) { DDataSet result= new DDataSet(1, 2, 1, 1, 1); if ( u!=null ) result.putProperty( QDataSet.UNITS, u ); result.putValue(0,min); result.putValue(1,max); result.putProperty( QDataSet.BINS_0, QDataSet.VALUE_BINS_MIN_MAX ); return result; } /** * Wraps an array from array of dimension sizes. The result will have * rank qube.length(). For rank 0, data is 1-element array. * @param data array containing the data, with the last dimension contiguous in memory. * @param qube array specifying the rank and size of each dimension * @return the array as a QDataSet */ public static DDataSet wrap( double[] data, int[] qube ) { switch (qube.length) { case 1: return new DDataSet( 1, qube[0], 1, 1, 1, data ); case 2: return new DDataSet( 2, qube[0], qube[1], 1, 1, data ); case 3: return new DDataSet( 3, qube[0], qube[1], qube[2], 1, data ); case 4: return new DDataSet( 4, qube[0], qube[1], qube[2], qube[3], data); case 0: // we support rank 0 with a 1-element array. return new DDataSet( 0, 1, 1, 1, 1, data ); default: throw new IllegalArgumentException("bad qube"); } } /** * wrap the array to make it into a dataset. This is the same as * wrap( back, qube ). The data is not copied, so be sure that no outside * codes modify the data unintentionally. * @param back the array * @param rank the rank of the dataset * @param len0 the length, or 1 if the index is not needed. * @param len1 the length, or 1 if the index is not needed. * @param len2 the length, or 1 if the index is not needed. * @return the dataset wrapping the array. */ public static DDataSet wrap( double[] back, int rank, int len0, int len1, int len2 ) { return new DDataSet( rank, len0, len1, len2, 1, back ); } protected DDataSet( int rank, int len0, int len1, int len2, int len3 ) { this( rank, len0, len1, len2, len3, new double[ len0 * len1 * len2 * len3] ); } protected DDataSet(int rank, int len0, int len1, int len2, int len3, double[] back) { super( double.class ); if ( back==null ) throw new NullPointerException("back was null"); this.back = back; if ( rank<0 ) throw new IllegalArgumentException("rank was -1"); this.rank = rank; this.len0 = len0; this.len1 = len1; this.len2 = len2; this.len3 = len3; if ( this.back.length < len0 * len1 * len2 * len3 ) { logger.warning("backing array appears to be too short"); } if ( rank>1 ) putProperty(QDataSet.QUBE, Boolean.TRUE); } @Override protected Object getBack() { checkImmutable(); return this.back; } @Override protected Object getBackReadOnly() { return this.back; } @Override protected int getBackJvmMemory() { return this.back.length * 8; } @Override protected Object getBackCopy() { Object newback = Array.newInstance( this.back.getClass().getComponentType(), this.back.length ); System.arraycopy( this.back, 0, newback, 0, this.back.length ); return newback; } @Override protected void setBack(Object back) { checkImmutable(); this.back= (double[])back; } @Override public double value() { if ( RANGE_CHECK ) { if ( this.rank!=0 ) { throw new IllegalArgumentException("rank 0 access on rank "+this.rank+" dataset"); } } return back[ 0 ]; } @Override public double value(int i0) { if (RANGE_CHECK) { if ( this.rank!=1 ) { throw new IllegalArgumentException("rank 1 access on rank "+this.rank+" dataset"); } if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } } return back[ i0 ]; } @Override public double value(int i0, int i1) { if (RANGE_CHECK) { if ( this.rank!=2 ) { throw new IllegalArgumentException("rank 2 access on rank "+this.rank+" dataset"); } if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } if (i1 < 0 || i1 >= len1) { throw new IndexOutOfBoundsException("i1=" + i1 + " " + this); } } return back[i0 * len1 + i1]; } @Override public double value(int i0, int i1, int i2) { if (RANGE_CHECK) { if ( this.rank!=3 ) { throw new IllegalArgumentException("rank 3 access on rank "+this.rank+" dataset"); } if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } if (i1 < 0 || i1 >= len1) { throw new IndexOutOfBoundsException("i1=" + i1 + " " + this); } if (i2 < 0 || i2 >= len2) { throw new IndexOutOfBoundsException("i2=" + i2 + " " + this); } } return back[i0 * len1 * len2 + i1 * len2 + i2]; } @Override public double value(int i0, int i1, int i2, int i3) { if (RANGE_CHECK) { if ( this.rank!=4 ) { throw new IllegalArgumentException("rank 4 access on rank "+this.rank+" dataset"); } if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } if (i1 < 0 || i1 >= len1) { throw new IndexOutOfBoundsException("i1=" + i1 + " " + this); } if (i2 < 0 || i2 >= len2) { throw new IndexOutOfBoundsException("i2=" + i2 + " " + this); } if (i3 < 0 || i3 >= len3) { throw new IndexOutOfBoundsException("i3=" + i3 + " " + this); } } return back[i0*len1*len2*len3 + i1*len2*len3 + i2*len3 +i3]; } @Override public void putValue( double value ) { checkImmutable(); if ( this.rank!=0 ) { throw new IllegalArgumentException("rank 0 putValue called on dataset that is rank "+this.rank+"."); } back[0]= value; } @Override public void putValue( int i0, double value ) { checkImmutable(); if (RANGE_CHECK) { if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } } back[i0] = value; } @Override public void putValue( int i0, int i1, double value ) { //checkImmutable(); if (RANGE_CHECK) { if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } if (i1 < 0 || i1 >= len1) { throw new IndexOutOfBoundsException("i1=" + i1 + " " + this); } } back[i0 * len1 + i1] = value; } @Override public void putValue( int i0, int i1, int i2, double value ) { checkImmutable(); if (RANGE_CHECK) { if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } if (i1 < 0 || i1 >= len1) { throw new IndexOutOfBoundsException("i1=" + i1 + " " + this); } if (i2 < 0 || i2 >= len2) { throw new IndexOutOfBoundsException("i2=" + i2 + " " + this); } } back[i0 * len1 * len2 + i1 * len2 + i2] = value; } @Override public void putValue( int i0, int i1, int i2, int i3, double value ) { checkImmutable(); if (RANGE_CHECK) { if (i0 < 0 || i0 >= len0) { throw new IndexOutOfBoundsException("i0=" + i0 + " " + this); } if (i1 < 0 || i1 >= len1) { throw new IndexOutOfBoundsException("i1=" + i1 + " " + this); } if (i2 < 0 || i2 >= len2) { throw new IndexOutOfBoundsException("i2=" + i2 + " " + this); } if (i3 < 0 || i3 >= len3) { throw new IndexOutOfBoundsException("i3=" + i3 + " " + this); } } back[ i0 * len1*len2*len3 + i1 * len2*len3 + i2 * len3 + i3 ] = value; } /** * add this value to the current value. * @param i0 the index * @param value the value, which is cast to this internal type. */ public void addValue( int i0, double value ) { checkImmutable(); back[ i0 ]+= value; } /** * add this value to the current value. * @param i0 the index * @param i1 the index * @param value the value, which is cast to this internal type. */ public void addValue( int i0, int i1, double value ) { if ( value!=0.0 ) { checkImmutable(); back[ i0 * len1 + i1 ]+= value; } } /** * add this value to the current value. * @param i0 the index * @param i1 the index * @param i2 the index * @param value the value, which is cast to this internal type. */ public void addValue( int i0, int i1, int i2, double value ) { checkImmutable(); back[ i0 * len1 * len2 + i1 * len2 + i2 ]+= value; } /** * add all valid values of ds to this dataset. * This does not reconcile rank, see CoerceUtil. * @param ds * @param wds * @see CoerceUtil#coerce(org.das2.qds.QDataSet, org.das2.qds.QDataSet, boolean, org.das2.qds.QDataSet[]) */ public void addValues( QDataSet ds, QDataSet wds ) { checkImmutable(); if ( wds==null ) { wds= Ops.valid(ds); } switch ( rank ) { case 1: for ( int i0=0; i00 ) { back[ i0 ]+= ds.value(i0) * w; } } break; case 2: for ( int i0=0; i00 ) { back[ i0 * len1 + i1 ]+= ds.value(i0,i1) * w; } } } break; case 3: for ( int i0=0; i00 ) { back[ i0 * len1 * len2 + i1 * len2 + i2 ]+= ds.value(i0,i1,i2) * w; } } } } break; case 4: for ( int i0=0; i00 ) { back[ i0 * len1 * len2 * len3 + i1 * len2 * len3 + i2 * len3 + i3 ]+= ds.value(i0,i1,i2,i3) * w; } } } } } break; default: throw new IllegalArgumentException("rank exception"); } } /** * add the value to the position. This is done all over the place with code like: * dd.putValue( i0,i1, dd.getValue( i0,i1 ) + z ) * which is inefficient. * @param i0 * @param value * @deprecated use addValue */ public void accumValue( int i0, double value ) { addValue(i0,value); } /** * add the value to the position. This is done all over the place with code like: * dd.putValue( i0,i1, dd.getValue( i0,i1 ) + z ) * which is inefficient. * @param i0 * @param i1 * @param value * @deprecated use addValue */ public void accumValue( int i0, int i1, double value ) { addValue( i0,i1,value ); } /** * creates a rank1 DDataSet by wrapping an existing array. * @param back the new backing array * @return the dataset */ public static DDataSet wrap( double[] back ) { return new DDataSet( 1, back.length, 1, 1, 1, back ); } /** * creates a DDataSet by wrapping an existing array, aliasing it to rank 2. * @param back the new backing array * @param nx number of elements in the zeroth index * @param ny number of elements in the first index * @return the dataset */ public static DDataSet wrap( double[] back, int nx, int ny ) { return new DDataSet( 2, nx, ny, 1, 1, back ); } public static DDataSet wrap( double[] back, int rank, int len0, int len1, int len2, int len3) { return new DDataSet( rank, len0, len1, len2, len3, back); } /** * useful create with units. * @param xx * @param xunits * @return */ public static DDataSet wrap(double[] xx, Units xunits) { DDataSet result= wrap( xx ); result.putProperty( QDataSet.UNITS, xunits ); return result; } /** * creates a DDataSet by wrapping an existing array, and aliasing it to rank2. * Note the last index is packed closest in memory. * @param back * @param n1 the size of the second dimension. * @return */ public static DDataSet wrapRank2(double[] back, int n1) { return new DDataSet(2, back.length / n1, n1, 1, 1, back); } /** * creates a DDataSet by wrapping an existing array, and aliasing it to rank2. * Note the last index is packed closest in memory. The first index length * is calculated from the size of the array. * @param back * @param n1 the size of the second index. * @param n2 the size of the third index. * @return */ public static DDataSet wrapRank3(double[] back, int n1, int n2) { return new DDataSet(3, back.length / (n1 * n2), n1, n2, 1, back); } /** * copy elements of src DDataSet into dest DDataSet, with System.arraycopy. * src and dst must have the same geometry, except for dim 0. Allows for * aliasing when higher dimension element count matches. * @param src source dataset * @param srcpos source dataset first dimension index. * @param dest destination dataset * @param nrec number of records to copy. Note this is different than the other copyElements! * @param destpos destination dataset first dimension index. * @throws IllegalArgumentException if the higher rank geometry doesn't match * @throws IndexOutOfBoundsException * @see #copyElements(org.das2.qds.DDataSet, int, org.das2.qds.DDataSet, int, int, boolean) */ public static void copyElements(DDataSet src, int srcpos, DDataSet dest, int destpos, int nrec ) { if ( src.len1 != dest.len1 || src.len2 != dest.len2 ) { throw new IllegalArgumentException("src and dest geometry don't match"); } copyElements( src, srcpos, dest, destpos, nrec * src.len1 * src.len2 * src.len3, false); } /** * copy elements of src DDataSet into dest DDataSet, with System.arraycopy. * src and dst must have the same geometry, except for dim 0. Allows for * aliasing when higher dimension element count matches. * @param src source dataset * @param srcpos source dataset first dimension index. * @param dest destination dataset * @param destpos destination dataset first dimension index. * @param len total number of elements to copy * @param checkAlias bounds for aliased write (same number of elements, different geometry.) * @throws IllegalArgumentException if the higher rank geometry doesn't match * @throws IndexOutOfBoundsException */ public static void copyElements( DDataSet src, int srcpos, DDataSet dest, int destpos, int len, boolean checkAlias ) { if ( checkAlias && ( src.len1*src.len2*src.len3 != dest.len1*dest.len2*dest.len3 ) ) { throw new IllegalArgumentException("src and dest geometry don't match"); } int srcpos1 = srcpos * src.len1 * src.len2 * src.len3; int destpos1 = destpos * dest.len1 * dest.len2 * dest.len3; int len1 = len; System.arraycopy( src.back, srcpos1, dest.back, destpos1, len1 ); } /** * the slice operator is better implemented here. Presently, we * use System.arraycopy to copy out the data, but this could be * re-implemented along with an offset parameter so the original data * can be used to back the data. * @param i the index * @return a rank N-1 slice of the data. */ @Override public QDataSet slice(int i) { if ( this.rank<1 ) { throw new IllegalArgumentException("slice called on rank 0 dataset"); } if ( this.len0<=i ) { throw new IndexOutOfBoundsException("out of bounds in slice of len0="+this.len0+" dataset: "+i); } int nrank = this.rank-1; int noff1= i * len1 * len2 * len3; int noff2= (i+1) * len1 * len2 * len3; double[] newback = new double[noff2-noff1]; System.arraycopy( this.back, noff1, newback, 0, noff2-noff1 ); Map props= DataSetOps.sliceProperties0(i,DataSetUtil.getProperties(this)); props= DataSetUtil.sliceProperties( this, i, props ); DDataSet result= new DDataSet( nrank, len1, len2, len3, 1, newback ); DataSetUtil.putProperties( props, result ); return result; } @Override public QDataSet trim(int start, int end) { if ( rank==0 ) { throw new IllegalArgumentException("trim called on rank 0 dataset"); } if ( start==0 && end==len0 ) { return this; } if ( RANGE_CHECK ) { if ( start>0 && len0==0 ) throw new IndexOutOfBoundsException("start>0 on a length=0 dataset" ); if ( start>len0 ) throw new IndexOutOfBoundsException("start="+start+" > "+len0 ); if ( start<0 ) throw new IndexOutOfBoundsException("start="+start+" < 0"); if ( end>len0 ) throw new IndexOutOfBoundsException("end="+end+" > "+len0 ); if ( end<0 ) throw new IndexOutOfBoundsException("end="+end+" < 0"); if ( start>end ) throw new IllegalArgumentException("trim called with start>end: "+start +">"+end); } int nrank = this.rank; int noff1= start * len1 * len2 * len3; int noff2= end * len1 * len2 * len3; double[] newback = new double[noff2-noff1]; if ( noff2-noff1>0 ) { System.arraycopy( this.back, noff1, newback, 0, noff2-noff1 ); } DDataSet result= new DDataSet( nrank, end-start, len1, len2, len3, newback ); Map props= DataSetUtil.getProperties(this); Map depProps= DataSetUtil.trimProperties( this, start, end ); props.putAll(depProps); DataSetUtil.putProperties( props, result ); return result; } /** * TODO: this is untested, but is left in to demonstrate how the capability * method should be implemented. Clients should use this instead of * casting the class to the capability class. * @param * @param clazz the class, such as WritableDataSet.class * @return null or the capability if exists, such as WritableDataSet */ @Override public T capability(Class clazz) { if ( clazz==WritableDataSet.class ) { if ( isImmutable() ) { return null; } else { return (T) this; } } else { return super.capability(clazz); } } }