All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dynatrace.dynahist.layout.LogOptimalLayout Maven / Gradle / Ivy

/*
 * Copyright 2020-2021 Dynatrace LLC
 *
 * Licensed 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 com.dynatrace.dynahist.layout;

import static com.dynatrace.dynahist.serialization.SerializationUtil.checkSerialVersion;
import static com.dynatrace.dynahist.serialization.SerializationUtil.writeSignedVarInt;
import static com.dynatrace.dynahist.util.Preconditions.checkArgument;

import com.dynatrace.dynahist.serialization.SerializationUtil;
import com.dynatrace.dynahist.util.Algorithms;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * A histogram bin layout where all bins covering the given range have a width that is either
 * smaller than a given absolute bin width limit or a given relative bin width limit. This layout is
 * optimal in terms of memory-efficiency. However, the mapping of values to bins is significantly
 * slower compared to {@link LogLinearLayout} and {@link LogQuadraticLayout}.
 *
 * 

This class is immutable. */ public final class LogOptimalLayout extends AbstractLayout { private static final byte SERIAL_VERSION_V0 = 0; private static final double LOG_MIN_VALUE = Math.log(Double.MIN_VALUE); private final double absoluteBinWidthLimit; private final double relativeBinWidthLimit; private final int underflowBinIndex; private final int overflowBinIndex; private final transient double factorNormal; private final transient double factorSubnormal; private final transient double offset; private final transient long unsignedValueBitsNormalLimit; /** * Creates a histogram bin layout covering a given range and with bins that have absolute and * relative width limitations. * *

The maximum bin width is either bounded by an absolute or a relative bin width limit. * * @param absoluteBinWidthLimit the absolute bin width limit * @param relativeBinWidthLimit the relative bin width limit * @param valueRangeLowerBound the range lower bound * @param valueRangeUpperBound the range upper bound * @return a new {@link LogOptimalLayout} instance */ public static LogOptimalLayout create( final double absoluteBinWidthLimit, final double relativeBinWidthLimit, final double valueRangeLowerBound, final double valueRangeUpperBound) { checkArgument(Double.isFinite(valueRangeUpperBound)); checkArgument(Double.isFinite(valueRangeLowerBound)); checkArgument(valueRangeUpperBound >= valueRangeLowerBound); checkArgument(absoluteBinWidthLimit >= Double.MIN_NORMAL); checkArgument(absoluteBinWidthLimit <= Double.MAX_VALUE); checkArgument(relativeBinWidthLimit >= 0); checkArgument(relativeBinWidthLimit <= Double.MAX_VALUE); final int firstNormalIdx = calculateFirstNormalIndex(relativeBinWidthLimit); // will always be >= 1 because 0 <= relativeBinWidthLimit <= Double.MAX_VALUE final double factorNormal = calculateFactorNormal(relativeBinWidthLimit); final double factorSubnormal = calculateFactorSubNormal(absoluteBinWidthLimit); final long unsignedValueBitsNormalLimit = calculateUnsignedValueBitsNormalLimit(factorSubnormal, firstNormalIdx); final double offset = calculateOffset(unsignedValueBitsNormalLimit, factorNormal, firstNormalIdx); final int valueRangeLowerBoundBinIndex = mapToBinIndex( valueRangeLowerBound, factorNormal, factorSubnormal, unsignedValueBitsNormalLimit, offset); final int valueRangeUpperBoundBinIndex = mapToBinIndex( valueRangeUpperBound, factorNormal, factorSubnormal, unsignedValueBitsNormalLimit, offset); checkArgument(valueRangeLowerBoundBinIndex > Integer.MIN_VALUE); checkArgument(valueRangeUpperBoundBinIndex < Integer.MAX_VALUE); final int underflowBinIndex = valueRangeLowerBoundBinIndex - 1; final int overflowBinIndex = valueRangeUpperBoundBinIndex + 1; checkArgument( (long) overflowBinIndex - (long) underflowBinIndex - 1L <= (long) Integer.MAX_VALUE); return new LogOptimalLayout( absoluteBinWidthLimit, relativeBinWidthLimit, underflowBinIndex, overflowBinIndex, factorNormal, factorSubnormal, offset, unsignedValueBitsNormalLimit); } private LogOptimalLayout( double absoluteBinWidthLimit, double relativeBinWidthLimit, int underflowBinIndex, int overflowBinIndex, double factorNormal, double factorSubnormal, double offset, long unsignedValueBitsNormalLimit) { this.absoluteBinWidthLimit = absoluteBinWidthLimit; this.relativeBinWidthLimit = relativeBinWidthLimit; this.underflowBinIndex = underflowBinIndex; this.overflowBinIndex = overflowBinIndex; this.factorNormal = factorNormal; this.factorSubnormal = factorSubnormal; this.offset = offset; this.unsignedValueBitsNormalLimit = unsignedValueBitsNormalLimit; } static long calculateUnsignedValueBitsNormalLimit(double factorSubnormal, int firstNormalIdx) { return Algorithms.findFirst( l -> calculateSubNormalIdx(Double.longBitsToDouble(l), factorSubnormal) >= firstNormalIdx, 0, Double.doubleToRawLongBits(Double.POSITIVE_INFINITY), calculateUnsignedValueBitsNormalLimitApproximate(factorSubnormal, firstNormalIdx)); } static long calculateUnsignedValueBitsNormalLimitApproximate( double factorSubnormal, int firstNormalIdx) { return Algorithms.mapDoubleToLong(firstNormalIdx / factorSubnormal); } static strictfp int calculateFirstNormalIndex(double relativeBinWidthLimit) { return (int) StrictMath.ceil(1. / relativeBinWidthLimit); } static strictfp double calculateFactorNormal(double relativeBinWidthLimit) { return 1. / StrictMath.log1p(relativeBinWidthLimit); } static strictfp double calculateFactorSubNormal(double absoluteBinWidthLimit) { return 1d / absoluteBinWidthLimit; } static double calculateOffset( long unsignedValueBitsNormalLimit, double factorNormal, int firstNormalIdx) { final double unsignedNormalLimit = Double.longBitsToDouble(unsignedValueBitsNormalLimit); return Algorithms.mapLongToDouble( Algorithms.findFirst( l -> { double offsetCandidate = Algorithms.mapLongToDouble(l); int binIndex = calculateNormalIdx(unsignedNormalLimit, factorNormal, offsetCandidate); return binIndex >= firstNormalIdx; }, Algorithms.NEGATIVE_INFINITY_MAPPED_TO_LONG, Algorithms.POSITIVE_INFINITY_MAPPED_TO_LONG, Algorithms.mapDoubleToLong( calculateOffsetApproximate(unsignedNormalLimit, factorNormal, firstNormalIdx)))); } static double calculateOffsetApproximate( double unsignedNormalLimit, double factorNormal, int firstNormalIdx) { return firstNormalIdx - factorNormal * mapToBinIndexHelper(unsignedNormalLimit); } /** * For unsigned positive values the return value is always nonnegative. * *

This function is monotonically increasing for all positive arguments. */ static double mapToBinIndexHelper(final double unsignedValue) { return Math.log(unsignedValue) - LOG_MIN_VALUE; } private static int calculateNormalIdx( final double unsignedValue, final double factorNormal, final double offset) { return (int) (factorNormal * mapToBinIndexHelper(unsignedValue) + offset); } private static int calculateSubNormalIdx( final double unsignedValue, final double factorSubnormal) { return (int) (factorSubnormal * unsignedValue); } // Unfortunately this mapping is not platform-independent. It would be independent if the strictfp // keyword was used for this method and all called methods. Due to a performance penalty (see // https://bugs.openjdk.java.net/browse/JDK-8136414) of strictfp, which is hopefully fixed in Java // 15, we have omitted strictfp here in the meantime. private static int mapToBinIndex( final double value, final double factorNormal, final double factorSubnormal, final long unsignedValueBitsNormalLimit, final double offset) { final long valueBits = Double.doubleToRawLongBits(value); final long unsignedValueBits = valueBits & 0x7fffffffffffffffL; final int idx; final double unsignedValue = Double.longBitsToDouble(unsignedValueBits); if (unsignedValueBits >= 0x7ff0000000000000L) { idx = 0x7fffffff; } else if (unsignedValueBits >= unsignedValueBitsNormalLimit) { idx = calculateNormalIdx(unsignedValue, factorNormal, offset); } else { idx = calculateSubNormalIdx(unsignedValue, factorSubnormal); } return (valueBits >= 0) ? idx : ~idx; } @Override public final int mapToBinIndex(final double value) { return mapToBinIndex( value, factorNormal, factorSubnormal, unsignedValueBitsNormalLimit, offset); } @Override public int getUnderflowBinIndex() { return underflowBinIndex; } @Override public int getOverflowBinIndex() { return overflowBinIndex; } public void write(DataOutput dataOutput) throws IOException { dataOutput.writeByte(SERIAL_VERSION_V0); dataOutput.writeDouble(absoluteBinWidthLimit); dataOutput.writeDouble(relativeBinWidthLimit); writeSignedVarInt(underflowBinIndex, dataOutput); writeSignedVarInt(overflowBinIndex, dataOutput); } public static LogOptimalLayout read(DataInput dataInput) throws IOException { checkSerialVersion(SERIAL_VERSION_V0, dataInput.readUnsignedByte()); double absoluteBinWidthLimitTmp = dataInput.readDouble(); double relativeBinWidthLimitTmp = dataInput.readDouble(); int underflowBinIndexTmp = SerializationUtil.readSignedVarInt(dataInput); int overflowBinIndexTmp = SerializationUtil.readSignedVarInt(dataInput); final int firstNormalIdxTmp = calculateFirstNormalIndex(relativeBinWidthLimitTmp); final double factorNormalTmp = calculateFactorNormal(relativeBinWidthLimitTmp); final double factorSubnormalTmp = calculateFactorSubNormal(absoluteBinWidthLimitTmp); final long unsignedValueBitsNormalLimitTmp = calculateUnsignedValueBitsNormalLimit(factorSubnormalTmp, firstNormalIdxTmp); final double offsetTmp = calculateOffset(unsignedValueBitsNormalLimitTmp, factorNormalTmp, firstNormalIdxTmp); return new LogOptimalLayout( absoluteBinWidthLimitTmp, relativeBinWidthLimitTmp, underflowBinIndexTmp, overflowBinIndexTmp, factorNormalTmp, factorSubnormalTmp, offsetTmp, unsignedValueBitsNormalLimitTmp); } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(absoluteBinWidthLimit); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + overflowBinIndex; temp = Double.doubleToLongBits(relativeBinWidthLimit); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + underflowBinIndex; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } LogOptimalLayout other = (LogOptimalLayout) obj; if (Double.doubleToLongBits(absoluteBinWidthLimit) != Double.doubleToLongBits(other.absoluteBinWidthLimit)) { return false; } if (overflowBinIndex != other.overflowBinIndex) { return false; } if (Double.doubleToLongBits(relativeBinWidthLimit) != Double.doubleToLongBits(other.relativeBinWidthLimit)) { return false; } if (underflowBinIndex != other.underflowBinIndex) { return false; } return true; } @Override protected double getBinLowerBoundApproximation(final int binIndex) { if (binIndex >= 0) { return getBinLowerBoundApproximationHelper(binIndex); } else { return -getBinLowerBoundApproximationHelper(-binIndex); } } private double getBinLowerBoundApproximationHelper(final int idx) { double x = idx * absoluteBinWidthLimit; if (x < Double.longBitsToDouble(unsignedValueBitsNormalLimit)) { return x; } else { final double s = (idx - offset) / factorNormal + LOG_MIN_VALUE; return Math.exp(s); } } @Override public String toString() { return getClass().getSimpleName() + " [absoluteBinWidthLimit=" + absoluteBinWidthLimit + ", relativeBinWidthLimit=" + relativeBinWidthLimit + ", underflowBinIndex=" + underflowBinIndex + ", overflowBinIndex=" + overflowBinIndex + "]"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy