// // Gridded1DSet.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad; import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.util.WeakHashMap; /** Gridded1DSet represents a finite set of samples of R.<P> */ public class Gridded1DSet extends GriddedSet implements Gridded1DSetIface { int LengthX; float LowX, HiX; /** Whether this set is ascending or descending */ boolean Ascending; /** * A canonicalizing cache of previously-created instances. Because instances * are immutable, a cache can be used to reduce memory usage by ensuring * that each instance is truly unique. By implementing the cache using a * {@link WeakHashMap}, this can be accomplished without the technique itself * adversely affecting memory usage. */ private static final WeakHashMap cache = new WeakHashMap(); /** * Constructs a 1-D sorted sequence with no regular interval. The * coordinate system and units are the default from the set type. The error * estimate is null. * * @param type The type of the set. Must be a {@link * RealType} or a single-component {@link * RealTupleType} or {@link SetType}. * @param samples The values in the set. * <code>samples[0][i]</code> is the value of * the ith sample point. Must be sorted (either * increasing or decreasing). May be * <code>null</code>. * @param lengthX The number of samples. */ public Gridded1DSet(MathType type, float[][] samples, int lengthX) throws VisADException { this(type, samples, lengthX, null, null, null); } /** * Constructs a 1-D sorted sequence with no regular interval. * * @param type The type of the set. Must be a {@link * RealType} or a single-component {@link * RealTupleType} or {@link SetType}. * @param samples The values in the set. * <code>samples[0][i]</code> is the value of * the ith sample point. Must be sorted (either * increasing or decreasing). May be * <code>null</code>. * @param lengthX The number of samples. * @param coord_sys The coordinate system for this, particular, set. * Must be compatible with the default coordinate * system. May be <code>null</code>. * @param units The units for the tuple components. Only * <code>units[0]</code> is meaningfull. Must * be compatible with the default unit. May be * <code>null</code>. * @param errors The error estimates of the tuple components. * Only <code>errors[0]</code> is meaningful. May * be <code>null</code>. */ public Gridded1DSet(MathType type, float[][] samples, int lengthX, CoordinateSystem coord_sys, Unit[] units, ErrorEstimate[] errors) throws VisADException { this(type, samples, lengthX, coord_sys, units, errors, true); } /** * Constructs a 1-D sorted sequence with no regular interval. * * @param type The type of the set. Must be a {@link * RealType} or a single-component {@link * RealTupleType} or {@link SetType}. * @param samples The values in the set. * <code>samples[0][i]</code> is the value of * the ith sample point. Must be sorted (either * increasing or decreasing). May be * <code>null</code>. * @param lengthX The number of samples. * @param coord_sys The coordinate system for this, particular, set. * Must be compatible with the default coordinate * system. May be <code>null</code>. * @param units The units for the tuple components. Only * <code>units[0]</code> is meaningfull. Must * be compatible with the default unit. May be * <code>null</code>. * @param errors The error estimates of the tuple components. * Only <code>errors[0]</code> is meaningful. May * be <code>null</code>. * @param copy Whether or not to copy the values array. */ public Gridded1DSet(MathType type, float[][] samples, int lengthX, CoordinateSystem coord_sys, Unit[] units, ErrorEstimate[] errors, boolean copy) throws VisADException { super(type, samples, make_lengths(lengthX), coord_sys, units, errors, copy); LowX = Low[0]; HiX = Hi[0]; LengthX = Lengths[0]; float[][]mySamples = getMySamples(); if (mySamples != null && Lengths[0] > 1) { // samples consistency test for (int i=0; i<Length; i++) { if (mySamples[0][i] != mySamples[0][i]) { throw new SetException( "Gridded1DSet: samples values may not be missing"); } } Ascending = (mySamples[0][LengthX-1] > mySamples[0][0]); if (Ascending) { for (int i=1; i<LengthX; i++) { if (mySamples[0][i] < mySamples[0][i-1]) { throw new SetException( "Gridded1DSet: samples do not form a valid grid ("+i+")"); } } } else { // !Pos for (int i=1; i<LengthX; i++) { if (mySamples[0][i] > mySamples[0][i-1]) { throw new SetException( "Gridded1DSet: samples do not form a valid grid ("+i+")"); } } } } } /** * Returns an instance of this class. This method uses a weak cache of * previously-created instances to reduce memory usage. * * @param type The type of the set. Must be a {@link * RealType} or a single-component {@link * RealTupleType} or {@link SetType}. * @param samples The values in the set. * <code>samples[i]</code> is the value of * the ith sample point. Must be sorted (either * increasing or decreasing). May be * <code>null</code>. The array is not copied, so * either don't modify it or clone it first. * @param coordSys The coordinate system for this, particular, set. * Must be compatible with the default coordinate * system. May be <code>null</code>. * @param unit The unit for the samples. Must be compatible * with the default unit. May be * <code>null</code>. * @param error The error estimate of the samples. May be * <code>null</code>. */ public static synchronized Gridded1DSet create( MathType type, float[] samples, CoordinateSystem coordSys, Unit unit, ErrorEstimate error) throws VisADException { Gridded1DSet newSet = new Gridded1DSet( type, new float[][] {samples}, samples.length, coordSys, new Unit[] {unit}, new ErrorEstimate[] {error}, false); WeakReference ref = (WeakReference)cache.get(newSet); if (ref == null) { /* * The new instance is unique (any and all previously-created identical * instances no longer exist). * * A WeakReference is used in the following because values of a * WeakHashMap aren't "weak" themselves and must not strongly reference * their associated keys either directly or indirectly. */ cache.put(newSet, new WeakReference(newSet)); } else { /* * The new instance is a duplicate of a previously-created one. */ Gridded1DSet oldSet = (Gridded1DSet)ref.get(); if (oldSet == null) { /* The previous instance no longer exists. Save the new instance. */ cache.put(newSet, new WeakReference(newSet)); } else { /* The previous instance still exists. Reuse it to save memory. */ newSet = oldSet; } } return newSet; } static int[] make_lengths(int lengthX) { int[] lens = new int[1]; lens[0] = lengthX; return lens; } /** convert an array of 1-D indices to an array of values in R^DomainDimension */ public float[][] indexToValue(int[] index) throws VisADException { int length = index.length; float[][]mySamples = getMySamples(); if (mySamples == null) { // not used - over-ridden by Linear1DSet.indexToValue float[][] grid = new float[ManifoldDimension][length]; for (int i=0; i<length; i++) { if (0 <= index[i] && index[i] < Length) { grid[0][i] = (float) index[i]; } else { grid[0][i] = -1; } } return gridToValue(grid); } else { float[][] values = new float[1][length]; for (int i=0; i<length; i++) { if (0 <= index[i] && index[i] < Length) { values[0][i] = mySamples[0][index[i]]; } else { values[0][i] = Float.NaN; } } return values; } } /** * Convert an array of values in R^DomainDimension to an array of * 1-D indices. This Gridded1DSet must have at least two points in the * set. * @param value An array of coordinates. <code>value[i][j] * </code> contains the <code>i</code>th component of the * <code>j</code>th point. * @return Indices of nearest points. RETURN_VALUE<code>[i]</code> * will contain the index of the point in the set closest * to <code>value[][i]</code> or <code>-1</code> if * <code>value[][i]</code> lies outside the set. */ public int[] valueToIndex(float[][] value) throws VisADException { if (value.length != DomainDimension) { throw new SetException("Gridded1DSet.valueToIndex: value dimension " + value.length + " not equal to Domain dimension " + DomainDimension); } int length = value[0].length; int[] index = new int[length]; float[][] grid = valueToGrid(value); float[] grid0 = grid[0]; float g; for (int i=0; i<length; i++) { g = grid0[i]; index[i] = Float.isNaN(g) ? -1 : ((int) (g + 0.5)); } return index; } /** transform an array of non-integer grid coordinates to an array of values in R^DomainDimension */ public float[][] gridToValue(float[][] grid) throws VisADException { if (grid.length < DomainDimension) { throw new SetException("Gridded1DSet.gridToValue: grid dimension " + grid.length + " not equal to Domain dimension " + DomainDimension); } /* remove DRM 2004-09-14 if (Lengths[0] < 2) { throw new SetException("Gridded1DSet.gridToValue: requires all grid " + "dimensions to be > 1"); } */ int length = grid[0].length; float[][] value = new float[1][length]; float[][]mySamples = getMySamples(); for (int i=0; i<length; i++) { // let g be the current grid coordinate float g = grid[0][i]; if ( (g < -0.5) || (g > LengthX-0.5) ) { value[0][i] = Float.NaN; } else if (Length == 1) { // just return the value if that's all we have value[0][i] = mySamples[0][0]; } else { // calculate closest integer variable int ig; if (g < 0) ig = 0; else if (g >= LengthX-1) ig = LengthX - 2; else ig = (int) g; float A = g - ig; // distance variable // do the linear interpolation value[0][i] = (1-A)*mySamples[0][ig] + A*mySamples[0][ig+1]; } } return value; } // WLH 6 Dec 2001 //private int ig = -1; /** transform an array of values in R^DomainDimension to an array of non-integer grid coordinates */ public float[][] valueToGrid(float[][] value) throws VisADException { if (value.length < DomainDimension) { throw new SetException("Gridded1DSet.valueToGrid: value dimension " + value.length + " not equal to Domain dimension " + DomainDimension); } /* remove DRM 2004-09-14 if (Lengths[0] < 2) { throw new SetException("Gridded1DSet.valueToGrid: requires all grid " + "dimensions to be > 1"); } */ float[] vals = value[0]; int length = vals.length; float[][]mySamples = getMySamples(); float[] samps = mySamples[0]; float[][] grid = new float[1][length]; int ig = (LengthX - 1)/2; /* WLH 6 Dec 2001 // use value from last call as first guess, if reasonable if (ig < 0 || ig >= LengthX) { ig = (LengthX - 1)/2; } */ for (int i=0; i<length; i++) { if (Float.isNaN(vals[i])) { grid[0][i] = Float.NaN; } else if (Length == 1) { // just return 0 if that's all we have grid[0][i] = 0; } else { int lower = 0; int upper = LengthX-1; while (lower < upper) { if ((vals[i] - samps[ig]) * (vals[i] - samps[ig+1]) <= 0) break; if (Ascending ? samps[ig+1] < vals[i] : samps[ig+1] > vals[i]) { lower = ig+1; } else if (Ascending ? samps[ig] > vals[i] : samps[ig] < vals[i]) { upper = ig; } if (lower < upper) ig = (lower + upper) / 2; } // Newton's method float solv = ig + (vals[i] - samps[ig]) / (samps[ig+1] - samps[ig]); if (solv > -0.5 && solv < LengthX - 0.5) grid[0][i] = solv; else { grid[0][i] = Float.NaN; // next guess should be in the middle if previous value was missing ig = (LengthX - 1)/2; } } } return grid; } public int getLengthX() { return LengthX; } public float getLowX() { return LowX; } public float getHiX() { return HiX; } public boolean isAscending() { return Ascending; } public Object cloneButType(MathType type) throws VisADException { return new Gridded1DSet(type, getMySamples(), Length, DomainCoordinateSystem, SetUnits, SetErrors); } /* run 'java visad.Gridded1DSet < formatted_input_stream' to test the Gridded1DSet class */ public static void main(String[] args) throws VisADException { // Define input stream InputStreamReader inStr = new InputStreamReader(System.in); // Define temporary integer array int[] ints = new int[80]; try { ints[0] = inStr.read(); } catch(Exception e) { System.out.println("Gridded1DSet: "+e); } int l = 0; while (ints[l] != 10) { try { ints[++l] = inStr.read(); } catch (Exception e) { System.out.println("Gridded1DSet: "+e); } } // convert array of integers to array of characters char[] chars = new char[l]; for (int i=0; i<l; i++) { chars[i] = (char) ints[i]; } int num_coords = Integer.parseInt(new String(chars)); // Define size of Samples array float[][] samp = new float[1][num_coords]; System.out.println("num_dimensions = 1, num_coords = "+num_coords+"\n"); // Skip blank line try { ints[0] = inStr.read(); } catch (Exception e) { System.out.println("Gridded1DSet: "+e); } for (int c=0; c<num_coords; c++) { l = 0; try { ints[0] = inStr.read(); } catch (Exception e) { System.out.println("Gridded1DSet: "+e); } while (ints[l] != 32) { try { ints[++l] = inStr.read(); } catch (Exception e) { System.out.println("Gridded1DSet: "+e); } } chars = new char[l]; for (int i=0; i<l; i++) { chars[i] = (char) ints[i]; } samp[0][c] = (Float.valueOf(new String(chars))).floatValue(); } // do EOF stuff try { inStr.close(); } catch (Exception e) { System.out.println("Gridded1DSet: "+e); } // Set up instance of Gridded1DSet RealType vis_data = RealType.getRealType("vis_data"); RealType[] vis_array = {vis_data}; RealTupleType vis_tuple = new RealTupleType(vis_array); Gridded1DSet gSet1D = new Gridded1DSet(vis_tuple, samp, num_coords); System.out.println("Lengths = " + num_coords + " wedge = "); int[] wedge = gSet1D.getWedge(); for (int i=0; i<wedge.length; i++) System.out.println(" " + wedge[i]); // Print out Samples information System.out.println("Samples ("+gSet1D.LengthX+"):"); for (int i=0; i<gSet1D.LengthX; i++) { System.out.println("#"+i+":\t"+gSet1D.getMySamples()[0][i]); } // Test gridToValue function System.out.println("\ngridToValue test:"); int myLength = gSet1D.LengthX+1; float[][] myGrid = new float[1][myLength]; for (int i=0; i<myLength; i++) { myGrid[0][i] = i-0.5f; } myGrid[0][0] += 0.1; // don't let grid values get too myGrid[0][myLength-1] -= 0.1; // close to interpolation limits float[][] myValue = gSet1D.gridToValue(myGrid); for (int i=0; i<myLength; i++) { System.out.println("("+((float) Math.round(1000000 *myGrid[0][i]) /1000000)+")\t--> " +((float) Math.round(1000000 *myValue[0][i]) /1000000)); } // Test valueToGrid function System.out.println("\nvalueToGrid test:"); float[][] gridTwo = gSet1D.valueToGrid(myValue); for (int i=0; i<gridTwo[0].length; i++) { System.out.println(((float) Math.round(1000000 *myValue[0][i]) /1000000)+" \t--> (" +((float) Math.round(1000000 *gridTwo[0][i]) /1000000)+")"); } System.out.println(); } /* Here's the output with sample file Gridded1D.txt: iris 25% java visad.Gridded1DSet < Gridded1D.txt num_dimensions = 1, num_coords = 20 Lengths = 20 wedge = 0 1 . . . 18 19 Samples (20): #0: -40.54849 #1: -39.462048 . . . #18: 26.026154 #19: 38.3012 gridToValue test: (-0.4) --> -40.983063 (0.5) --> -40.00527 . . . (18.5) --> 32.163677 (19.4) --> 43.211212 valueToGrid test: -40.983063 --> (-0.399998) -40.00527 --> (0.5) . . . 32.163677 --> (18.5) 43.211212 --> (19.4) iris 26% */ }