/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.das2.datum; import java.util.ArrayList; import java.util.List; import org.das2.datum.format.DatumFormatter; import org.das2.datum.format.TimeDatumFormatter; /** * * @author jbf */ public class OrdinalTimeDomainDivider implements DomainDivider { /** * indicates if the first ordinal value of the field is zero or one. */ private final static int[] ZEROONE = new int[]{0, 1, 1, 0, 0, 0, 0}; private final static int[] MODULO = new int[]{10000, 12, 30, 24, 60, 60, 1000000000 }; private static final int N_DIGITS = 7; private final static List[] FACTORS = new List[N_DIGITS]; static { for (int i = 0; i < N_DIGITS; i++) { FACTORS[i] = factors(primeFactors(MODULO[i])); // 10,000 years } } private int significand; private int digit; private final static int ARR_YEAR = 0; private final static int ARR_MONTH = 1; private final static int ARR_DAY = 2; private final static int ARR_HOUR = 3; private final static int ARR_MINUTE = 4; private final static int ARR_SECOND = 5; private final static int ARR_NANOS = 6; /** * years and seconds divider, which should be used if non-null. */ private LinearDomainDivider ysDivider; /** * * @param significand multiplier for digit. * @param digit ordinal ARR_YEAR, ARR_MONTH, etc. */ private OrdinalTimeDomainDivider(int significand, int digit, LinearDomainDivider secondsDivider ) { this.significand = significand; this.digit = digit; this.ysDivider= secondsDivider; } protected OrdinalTimeDomainDivider() { this(1, TimeUtil.HOUR - 1, null ); } /** * return a list of the prime factors for the number. * @param N * @return */ private static List primeFactors(int N) { List result = new ArrayList<>(); while (N % 10 == 0) { // favor tens result.add(2); result.add(5); N = N / 10; } for (int i = 2; i <= N; i++) { while (N % i == 0) { result.add(i); N = N / i; } } return result; } /** * calculate a list of all factors from the prime factors. I'm sure there * is a more elegant way of doing this. * @param primeFactors, the prime factors of the digit, sorted. e.g. base 10->2,5 24->2,2,2,3 * @return a list of factors for the digit. */ private static List factors(List primeFactors) { List result = new ArrayList<>(); result.add(1); int count = 1; int c = (int) Math.pow(2, primeFactors.size()); //I'm sure there is a more elegant way of doing this, probably skipping when the lower digits reset to zero. for (int i = 0; i < c; i++) { int r1 = 1; String sb = Integer.toBinaryString(i); int nsb = sb.length() - 1; for (int j = 0; j < sb.length(); j++) { if (sb.charAt(nsb - j) == '1') { r1 *= primeFactors.get(j); } } if (r1 > result.get(count - 1)) { result.add(r1); count++; } } result.remove(result.size() - 1); // don't include the number itsself. return result; } @Override public DomainDivider finerDivider(boolean superset) { int newSignificand; int newDigit = digit; if ( ysDivider!=null ) { if ( digit==ARR_YEAR && ( ysDivider.getSignificand()>1 || ysDivider.getExponent()>0 ) ) { return new OrdinalTimeDomainDivider( significand, digit, (LinearDomainDivider)ysDivider.finerDivider(superset) ); } else if ( digit==ARR_SECOND ) { if ( Math.abs( ysDivider.getExponent() ) > 1000 ) { throw new IllegalArgumentException("something has gone wrong in OrdinalTimeDomainDivider"); } return new OrdinalTimeDomainDivider( significand, digit, (LinearDomainDivider)ysDivider.finerDivider(superset) ); } } List factors = FACTORS[digit]; int i = factors.indexOf(significand); do { i = i - 1; if (i == -1) { newDigit = digit + 1; factors = FACTORS[newDigit]; newSignificand = factors.get(factors.size() - 1); if ( newDigit==ARR_DAY && superset ) { newSignificand= 1; } break; } else { newSignificand = factors.get(i); } } while (superset && significand % newSignificand > 0); if ( newDigit==ARR_SECOND && newSignificand==1. ) { return new OrdinalTimeDomainDivider(newSignificand, newDigit, new LinearDomainDivider() ); //TODO: this assumes 1e0 } else { return new OrdinalTimeDomainDivider(newSignificand, newDigit, null ); } } @Override public DomainDivider coarserDivider(boolean superset) { int newSignificand = 0; int newDigit = digit; List factors = FACTORS[digit]; int i = factors.indexOf(significand); if ( ysDivider!=null ) { if ( digit==ARR_SECOND && ysDivider.getExponent()<0 ) { return new OrdinalTimeDomainDivider( significand, digit, (LinearDomainDivider)ysDivider.coarserDivider(superset) ); } else if ( digit==ARR_YEAR ) { return new OrdinalTimeDomainDivider( significand, digit, (LinearDomainDivider)ysDivider.coarserDivider(superset) ); } } do { i = i + 1; if (i == factors.size()) { //newDigit = digit - 1; //newSignificand = 1; if ( digit==ARR_DAY && superset ) { newDigit = digit - 1; newSignificand = 1; // This breaks the superset contract. Where will this haunt us? } else { newDigit = digit - 1; newSignificand = 1; } break; } else { newSignificand = factors.get(i); } } while (superset && newSignificand % significand > 0); if ( newDigit==ARR_YEAR && newSignificand==1. ) { return new OrdinalTimeDomainDivider(newSignificand, newDigit, new LinearDomainDivider() ); //TODO: this assumes 1e0 } else { return new OrdinalTimeDomainDivider(newSignificand, newDigit, null ); } } /** * take the floor of the interval, mutating the input array. The array is returned for convenience. * @param tarr * @param significand * @param digit * @return the input array which has been mutated. */ private static int[] floor(int[] tarr, int significand, int digit) { tarr[digit] = (tarr[digit] - ZEROONE[digit]) / significand * significand + ZEROONE[digit]; System.arraycopy(ZEROONE, digit + 1, tarr, digit + 1, N_DIGITS - (digit + 1)); return tarr; } /** * take the ceil of the interval, mutating the input array. The array is returned for convenience. * @param tarr * @param significand * @param digit * @return the input array which has been mutated. */ private static int[] ceil(int[] tarr, int significand, int digit) { int d0 = tarr[digit] - ZEROONE[digit]; int ceil = d0 % significand == 0 ? 0 : 1; // ceil==0 means digit is at the boundary, ceil=1 means we'll round up. for (int i = digit + 1; i < N_DIGITS; i++) { if (tarr[i] > ZEROONE[i]) { ceil = 1; } tarr[i] = ZEROONE[i]; } tarr[digit] = (d0 / significand + ceil) * significand + ZEROONE[digit]; carry(tarr); return tarr; } /** * carry digit significands where possible, e.g. 24 hours -> day++ * @param tarr * @return */ private static int[] carry(int[] tarr) { for (int i = ARR_SECOND; i > ARR_DAY; i--) { if (tarr[i] > MODULO[i]) { tarr[i - 1]++; tarr[i] -= MODULO[i]; } } if (tarr[ARR_MONTH] > 12) { tarr[ARR_MONTH] -= 12; tarr[ARR_YEAR]++; } int dim= TimeUtil.daysInMonth(tarr[ARR_MONTH], tarr[ARR_YEAR]); if (tarr[ARR_DAY] > dim ) { tarr[ARR_MONTH]++; tarr[ARR_DAY]-= dim; } if (tarr[ARR_MONTH] > 12) { tarr[ARR_MONTH] -= 12; tarr[ARR_YEAR]++; } return tarr; } @Override public DatumVector boundaries(Datum min, Datum max) { if ( !min.isFinite() || !max.isFinite() ) { System.err.println("min and max must be finite"); // throw new IllegalArgumentException("min and max must be finite" ); } if ( digit==ARR_SECOND && ysDivider!=null ) { Datum t= TimeUtil.prevMidnight( min ).convertTo(Units.t2000); DatumVector secs= ysDivider.boundaries(min.subtract(t).convertTo(Units.seconds), max.subtract(t).convertTo(Units.seconds) ); return secs.add(t); } if ( digit==ARR_YEAR && ysDivider!=null ) { int yearMin = TimeUtil.toTimeStruct(min).year; int yearMax = TimeUtil.toTimeStruct(max).year; Units u= Units.dimensionless; DatumVector years= ysDivider.boundaries( u.createDatum(yearMin), u.createDatum(yearMax) ); Units out= min.getUnits(); double[] tickV = years.toDoubleArray(Units.dimensionless); for (int i = 0; i < tickV.length; i++) { int iyear = (int) tickV[i]; tickV[i] = TimeUtil.convert(iyear, 1, 1, 0, 0, 0, (TimeLocationUnits) out ); } return DatumVector.newDatumVector(tickV,out); } if (digit == ARR_DAY) { long nb = boundaryCount(min, max); if (nb > MAX_BOUNDARIES) { throw new IllegalArgumentException("LinearDomainDivider: too many divisions requested (" + boundaryCount(min, max) + ")"); } double[] values = new double[(int) nb]; Units units = min.getUnits(); int[] tmin = ceil( TimeUtil.toTimeArray(min), 1, digit ); tmin = ceil( tmin, significand, digit ); for (int i = 0; i < nb; i++) { values[i] = TimeUtil.toDatum(tmin).doubleValue(units); tmin[digit]+= significand; carry(tmin); } return DatumVector.newDatumVector(values, units); } else { long nb = boundaryCount(min, max); if (nb > MAX_BOUNDARIES) { throw new IllegalArgumentException("LinearDomainDivider: too many divisions requested (" + boundaryCount(min, max) + ")"); } double[] values = new double[(int) nb]; Units units = min.getUnits(); int[] tmin = ceil(TimeUtil.toTimeArray(min), significand, digit); for (int i = 0; i < nb; i++) { values[i] = TimeUtil.toDatum(tmin).doubleValue(units); tmin[digit] = tmin[digit] + significand; carry(tmin); } return DatumVector.newDatumVector(values, units); } } @Override public long boundaryCount(Datum min, Datum max) { if ( digit==ARR_SECOND && ysDivider!=null ) { Datum t= TimeUtil.prevMidnight( min ).convertTo(Units.t2000); return ysDivider.boundaryCount( min.subtract(t).convertTo(Units.seconds), max.subtract(t).convertTo(Units.seconds) ); } if ( digit==ARR_YEAR && ysDivider!=null ) { int yearMin = TimeUtil.toTimeStruct(min).year; int yearMax = TimeUtil.toTimeStruct(max).year; Units u= Units.dimensionless; return ysDivider.boundaryCount( u.createDatum(yearMin), u.createDatum(yearMax) ); } int[] tmin = ceil(TimeUtil.toTimeArray(min), significand, digit); int[] tmax = floor(TimeUtil.toTimeArray(max), significand, digit); // branch to avoid crossing over days, which have no modulo. if (digit < ARR_DAY) { long result = tmax[0] - tmin[0]; for (int i = ARR_MONTH; i <= digit; i++) { result = result * MODULO[i] + (tmax[i] - tmin[i]); } return (result / significand) + 1; } else if (digit == ARR_DAY) { tmin = ceil(TimeUtil.toTimeArray(min), 1, digit); tmax = floor(TimeUtil.toTimeArray(max), 1, digit); int jmin = TimeUtil.julianDay(tmin[ARR_YEAR], tmin[ARR_MONTH], tmin[ARR_DAY]); int jmax = TimeUtil.julianDay(tmax[ARR_YEAR], tmax[ARR_MONTH], tmax[ARR_DAY]); long result = ((jmax - jmin) / significand) + 1; return result; } else { int jmin = TimeUtil.julianDay(tmin[ARR_YEAR], tmin[ARR_MONTH], tmin[ARR_DAY]); int jmax = TimeUtil.julianDay(tmax[ARR_YEAR], tmax[ARR_MONTH], tmax[ARR_DAY]); long result = jmax - jmin; for (int i = ARR_HOUR; i <= digit; i++) { result = result * MODULO[i] + (tmax[i] - tmin[i]); } return (result / significand) + 1; } } @Override public DatumRange rangeContaining(Datum v) { if ( digit==ARR_SECOND && ysDivider!=null ) { Datum t= TimeUtil.prevMidnight( v ); DatumRange r= ysDivider.rangeContaining( v.subtract(t).convertTo(Units.seconds) ); return new DatumRange( t.add(r.min()), t.add(r.max()) ); } if ( digit==ARR_YEAR && ysDivider!=null ) { int yearMin = TimeUtil.toTimeStruct(v).year; DatumRange r= ysDivider.rangeContaining( Units.dimensionless.createDatum(yearMin) ); double tmin, tmax; TimeLocationUnits out= (TimeLocationUnits) v.getUnits(); tmin = TimeUtil.convert((int)r.min().doubleValue(), 1, 1, 0, 0, 0, out ); tmax = TimeUtil.convert((int)r.max().doubleValue(), 1, 1, 0, 0, 0, out ); return new DatumRange( tmin, tmax, out ); } int[] tarr = floor(TimeUtil.fromDatum(v), significand, digit); Datum dstart = TimeUtil.toDatum(tarr); tarr[digit] = tarr[digit] + significand; Datum dstop = TimeUtil.toDatum(tarr); return new DatumRange(dstart, dstop); } @Override public String toString() { if ( ysDivider!=null ) { return "OTDomainDivider delegate offset to "+ysDivider + " "+ TimeUtil.TimeDigit.fromOrdinal(digit + 1) ; } else { return "OTDomainDivider by " + significand + " " + TimeUtil.TimeDigit.fromOrdinal(digit + 1); } } public DatumFormatter getFormatter( DatumRange range ) { if ( ysDivider!=null ) { if ( digit==ARR_SECOND ) { if ( ysDivider.getExponent()<-6 ) { return TimeDatumFormatter.NANOSECONDS; } else if ( ysDivider.getExponent()<-3 ) { return TimeDatumFormatter.MICROSECONDS; } else if ( ysDivider.getExponent()<0 ) { return TimeDatumFormatter.MILLISECONDS; } else { return TimeDatumFormatter.SECONDS; } } else { return TimeDatumFormatter.YEARS; } } else { return TimeDatumFormatter.formatterForScale( digit+1, range); } } public static void main(String[] args) throws Exception { System.err.println(primeFactors(1000)); // logger okay List factors = factors(primeFactors(1000)); for (int i = 0; i < factors.size(); i++) { System.err.print(" " + factors.get(i)); // logger okay } System.err.println(""); // logger okay DomainDivider div = new OrdinalTimeDomainDivider(); DatumRange dr = DatumRangeUtil.parseTimeRange("2009"); System.err.println(div.boundaryCount(dr.min(), dr.max())); // logger okay System.err.println(div.boundaries(dr.min(), dr.max())); // logger okay System.err.println(div.rangeContaining(dr.min())); // logger okay System.err.println(div.coarserDivider(false).boundaryCount(dr.min(), dr.max())); // logger okay System.err.println(div.finerDivider(false).boundaryCount(dr.min(), dr.max())); // logger okay div = new OrdinalTimeDomainDivider(1000, ARR_YEAR,null); for (int i = 0; i < 100; i++) { System.err.println(div); // logger okay div = div.finerDivider(false); } for (int i = 0; i < 100; i++) { div = div.coarserDivider(false); System.err.println(div); // logger okay } for (int i = 0; i < 30; i++) { System.err.println(div); // logger okay div = div.finerDivider(true); } for (int i = 0; i < 30; i++) { div = div.coarserDivider(true); System.err.println(div); // logger okay } } }