From 3a3a5bd77f02b8a1fc43c399c53a5086a7891d5c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 23 Mar 2022 14:07:24 +0300 Subject: [PATCH] Histogram API refactor --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../space/kscience/kmath/domains/Domain1D.kt | 2 +- .../kscience/kmath/histogram/Histogram.kt | 2 +- .../kscience/kmath/histogram/Histogram1D.kt | 4 +- .../kmath/histogram/IndexedHistogramSpace.kt | 2 +- .../histogram/MultivariateHistogramTest.kt | 6 +- .../kmath/histogram/TreeHistogramSpace.kt | 70 +++++++++++-------- 7 files changed, 49 insertions(+), 39 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b..00e33edef 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt index f50f16c11..ccd1c3edb 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt @@ -38,5 +38,5 @@ public class DoubleDomain1D( } @UnstableKMathAPI -public val DoubleDomain1D.center: Double +public val Domain1D.center: Double get() = (range.endInclusive + range.start) / 2 diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram.kt index 64c031c7a..f9550df17 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram.kt @@ -17,7 +17,7 @@ public interface Bin : Domain { /** * The value of this bin. */ - public val value: V + public val binValue: V } public interface Histogram> { diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram1D.kt index e9c62b141..4f193f943 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram1D.kt @@ -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, out V>( public val domain: Domain1D, - override val value: V, + override val binValue: V, ) : Bin, ClosedRange by domain.range { override val dimension: Int get() = 1 diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt index a2623b1a1..3e4b07984 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt @@ -20,7 +20,7 @@ import space.kscience.kmath.operations.invoke */ public data class DomainBin, out V>( public val domain: Domain, - override val value: V, + override val binValue: V, ) : Bin, Domain by domain /** diff --git a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/MultivariateHistogramTest.kt b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/MultivariateHistogramTest.kt index 923cc98de..31a29676c 100644 --- a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/MultivariateHistogramTest.kt @@ -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() }) } } } \ No newline at end of file diff --git a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt index 9d07d5014..38e533db0 100644 --- a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt +++ b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt @@ -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 > TreeMap.getBin(value: Double): return null } +public data class ValueAndError(val value: Double, val error: Double) + +public typealias WeightedBin1D = Bin1D + @UnstableKMathAPI public class TreeHistogram( - private val binMap: TreeMap>, -) : Histogram1D { - override fun get(value: Double): Bin1D? = binMap.getBin(value) - override val bins: Collection> get() = binMap.values + private val binMap: TreeMap, +) : Histogram1D { + override fun get(value: Double): WeightedBin1D? = binMap.getBin(value) + override val bins: Collection get() = binMap.values } @OptIn(UnstableKMathAPI::class) @PublishedApi internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) : Histogram1DBuilder { + override val defaultValue: Double get() = 1.0 + internal class BinCounter(val domain: DoubleDomain1D, val counter: Counter = Counter.double()) : ClosedRange 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>()) { (_, binCounter) -> - val count = binCounter.counter.value - Bin1D(binCounter.domain, count, sqrt(count)) + val map = bins.mapValuesTo(TreeMap()) { (_, 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>, ScaleOperations> { +) : Group, ScaleOperations { - public inline fun fill(block: Histogram1DBuilder.() -> Unit): Histogram1D = + public inline fun fill(block: Histogram1DBuilder.() -> Unit): TreeHistogram = TreeHistogramBuilder(binFactory).apply(block).build() override fun add( - left: Histogram1D, - right: Histogram1D, - ): Histogram1D { + 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>().apply { + val bins = TreeMap().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, value: Double): Histogram1D { - val bins = TreeMap>().apply { + override fun scale(a: TreeHistogram, value: Double): TreeHistogram { + val bins = TreeMap().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.unaryMinus(): Histogram1D = this * (-1) + override fun TreeHistogram.unaryMinus(): TreeHistogram = this * (-1) - override val zero: Histogram1D 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.() -> Unit, - ): Histogram1D = uniform(binSize, start).fill(builder) + builder: Histogram1DBuilder.() -> 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.() -> Unit, - ): Histogram1D = custom(borders).fill(builder) + builder: Histogram1DBuilder.() -> Unit, + ): TreeHistogram = custom(borders).fill(builder) /**