Histogram API refactor

This commit is contained in:
Alexander Nozik 2022-03-23 14:07:24 +03:00
parent 29369cd6d7
commit 3a3a5bd77f
7 changed files with 49 additions and 39 deletions

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -38,5 +38,5 @@ public class DoubleDomain1D(
}
@UnstableKMathAPI
public val DoubleDomain1D.center: Double
public val Domain1D<Double>.center: Double
get() = (range.endInclusive + range.start) / 2

View File

@ -17,7 +17,7 @@ public interface Bin<in T : Any, out V> : Domain<T> {
/**
* The value of this bin.
*/
public val value: V
public val binValue: V
}
public interface Histogram<in T : Any, out V, out B : Bin<T, V>> {

View File

@ -14,13 +14,13 @@ import space.kscience.kmath.structures.Buffer
/**
* A univariate bin based on a range
*
* @property value The value of histogram including weighting
* @property binValue The value of histogram including weighting
* @property standardDeviation Standard deviation of the bin value. Zero or negative if not applicable
*/
@UnstableKMathAPI
public class Bin1D<T : Comparable<T>, out V>(
public val domain: Domain1D<T>,
override val value: V,
override val binValue: V,
) : Bin<T, V>, ClosedRange<T> by domain.range {
override val dimension: Int get() = 1

View File

@ -20,7 +20,7 @@ import space.kscience.kmath.operations.invoke
*/
public data class DomainBin<in T : Comparable<T>, out V>(
public val domain: Domain<T>,
override val value: V,
override val binValue: V,
) : Bin<T, V>, Domain<T> by domain
/**

View File

@ -21,7 +21,7 @@ internal class MultivariateHistogramTest {
val histogram = hSpace.produce {
put(0.55, 0.55)
}
val bin = histogram.bins.find { it.value.toInt() > 0 } ?: fail()
val bin = histogram.bins.find { it.binValue.toInt() > 0 } ?: fail()
assertTrue { bin.contains(DoubleVector(0.55, 0.55)) }
assertTrue { bin.contains(DoubleVector(0.6, 0.5)) }
assertFalse { bin.contains(DoubleVector(-0.55, 0.55)) }
@ -44,7 +44,7 @@ internal class MultivariateHistogramTest {
put(nextDouble(), nextDouble(), nextDouble())
}
}
assertEquals(n, histogram.bins.sumOf { it.value.toInt() })
assertEquals(n, histogram.bins.sumOf { it.binValue.toInt() })
}
@Test
@ -77,7 +77,7 @@ internal class MultivariateHistogramTest {
assertTrue {
res.bins.count() >= histogram1.bins.count()
}
assertEquals(0.0, res.bins.sumOf { it.value.toDouble() })
assertEquals(0.0, res.bins.sumOf { it.binValue.toDouble() })
}
}
}

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.histogram
import space.kscience.kmath.domains.DoubleDomain1D
import space.kscience.kmath.domains.center
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.Group
import space.kscience.kmath.operations.ScaleOperations
@ -26,18 +27,24 @@ private fun <B : ClosedRange<Double>> TreeMap<Double, B>.getBin(value: Double):
return null
}
public data class ValueAndError(val value: Double, val error: Double)
public typealias WeightedBin1D = Bin1D<Double, ValueAndError>
@UnstableKMathAPI
public class TreeHistogram(
private val binMap: TreeMap<Double, out Bin1D<Double, Double>>,
) : Histogram1D<Double, Double> {
override fun get(value: Double): Bin1D<Double, Double>? = binMap.getBin(value)
override val bins: Collection<Bin1D<Double, Double>> get() = binMap.values
private val binMap: TreeMap<Double, WeightedBin1D>,
) : Histogram1D<Double, ValueAndError> {
override fun get(value: Double): WeightedBin1D? = binMap.getBin(value)
override val bins: Collection<WeightedBin1D> get() = binMap.values
}
@OptIn(UnstableKMathAPI::class)
@PublishedApi
internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) : Histogram1DBuilder<Double, Double> {
override val defaultValue: Double get() = 1.0
internal class BinCounter(val domain: DoubleDomain1D, val counter: Counter<Double> = Counter.double()) :
ClosedRange<Double> by domain.range
@ -46,7 +53,7 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D)
fun get(value: Double): BinCounter? = bins.getBin(value)
fun createBin(value: Double): BinCounter {
val binDefinition = binFactory(value)
val binDefinition: DoubleDomain1D = binFactory(value)
val newBin = BinCounter(binDefinition)
synchronized(this) {
bins[binDefinition.center] = newBin
@ -69,9 +76,9 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D)
}
fun build(): TreeHistogram {
val map = bins.mapValuesTo(TreeMap<Double, Bin1D<Double,Double>>()) { (_, binCounter) ->
val count = binCounter.counter.value
Bin1D(binCounter.domain, count, sqrt(count))
val map = bins.mapValuesTo(TreeMap<Double, WeightedBin1D>()) { (_, binCounter) ->
val count: Double = binCounter.counter.value
WeightedBin1D(binCounter.domain, ValueAndError(count, sqrt(count)))
}
return TreeHistogram(map)
}
@ -83,26 +90,27 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D)
@UnstableKMathAPI
public class TreeHistogramSpace(
@PublishedApi internal val binFactory: (Double) -> DoubleDomain1D,
) : Group<Histogram1D<Double,Double>>, ScaleOperations<Histogram1D<Double,Double>> {
) : Group<TreeHistogram>, ScaleOperations<TreeHistogram> {
public inline fun fill(block: Histogram1DBuilder<Double,Double>.() -> Unit): Histogram1D<Double,Double> =
public inline fun fill(block: Histogram1DBuilder<Double, Double>.() -> Unit): TreeHistogram =
TreeHistogramBuilder(binFactory).apply(block).build()
override fun add(
left: Histogram1D<Double,Double>,
right: Histogram1D<Double,Double>,
): Histogram1D<Double,Double> {
left: TreeHistogram,
right: TreeHistogram,
): TreeHistogram {
// require(a.context == this) { "Histogram $a does not belong to this context" }
// require(b.context == this) { "Histogram $b does not belong to this context" }
val bins = TreeMap<Double, Bin1D<Double,Double>>().apply {
val bins = TreeMap<Double, WeightedBin1D>().apply {
(left.bins.map { it.domain } union right.bins.map { it.domain }).forEach { def ->
put(
def.center,
Bin1D(
WeightedBin1D(
def,
value = (left[def.center]?.value ?: 0.0) + (right[def.center]?.value ?: 0.0),
standardDeviation = (left[def.center]?.standardDeviation
?: 0.0) + (right[def.center]?.standardDeviation ?: 0.0)
ValueAndError(
(left[def.center]?.binValue?.value ?: 0.0) + (right[def.center]?.binValue?.value ?: 0.0),
(left[def.center]?.binValue?.error ?: 0.0) + (right[def.center]?.binValue?.error ?: 0.0)
)
)
)
}
@ -110,15 +118,17 @@ public class TreeHistogramSpace(
return TreeHistogram(bins)
}
override fun scale(a: Histogram1D<Double,Double>, value: Double): Histogram1D<Double,Double> {
val bins = TreeMap<Double, Bin1D<Double,Double>>().apply {
override fun scale(a: TreeHistogram, value: Double): TreeHistogram {
val bins = TreeMap<Double, WeightedBin1D>().apply {
a.bins.forEach { bin ->
put(
bin.domain.center,
Bin1D(
WeightedBin1D(
bin.domain,
value = bin.value * value,
standardDeviation = abs(bin.standardDeviation * value)
ValueAndError(
bin.binValue.value * value,
abs(bin.binValue.error * value)
)
)
)
}
@ -127,27 +137,27 @@ public class TreeHistogramSpace(
return TreeHistogram(bins)
}
override fun Histogram1D<Double,Double>.unaryMinus(): Histogram1D<Double,Double> = this * (-1)
override fun TreeHistogram.unaryMinus(): TreeHistogram = this * (-1)
override val zero: Histogram1D<Double,Double> by lazy { fill { } }
override val zero: TreeHistogram by lazy { fill { } }
public companion object {
/**
* Build and fill a [DoubleHistogram1D]. Returns a read-only histogram.
* Build and fill a [TreeHistogram]. Returns a read-only histogram.
*/
public inline fun uniform(
binSize: Double,
start: Double = 0.0,
builder: Histogram1DBuilder<Double,Double>.() -> Unit,
): Histogram1D<Double,Double> = uniform(binSize, start).fill(builder)
builder: Histogram1DBuilder<Double, Double>.() -> Unit,
): TreeHistogram = uniform(binSize, start).fill(builder)
/**
* Build and fill a histogram with custom borders. Returns a read-only histogram.
*/
public inline fun custom(
borders: DoubleArray,
builder: Histogram1DBuilder<Double,Double>.() -> Unit,
): Histogram1D<Double,Double> = custom(borders).fill(builder)
builder: Histogram1DBuilder<Double, Double>.() -> Unit,
): TreeHistogram = custom(borders).fill(builder)
/**