From 39640498fc93f4ca5adc4c439c641d5aeb4510cb Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 19 Mar 2022 09:20:32 +0300 Subject: [PATCH 01/14] Another histogram refactor --- build.gradle.kts | 2 +- buildSrc/gradle.properties | 2 +- .../kmath/domains/HyperSquareDomain.kt | 11 ++++- .../kmath/histogram/DoubleHistogramSpace.kt | 46 +++++++++++-------- .../kmath/histogram/IndexedHistogramSpace.kt | 20 ++++---- .../kmath/histogram/UnivariateHistogram.kt | 20 -------- .../kmath/histogram/TreeHistogramSpace.kt | 22 ++++++++- .../kmath/histogram/TreeHistogramTest.kt | 2 +- 8 files changed, 71 insertions(+), 54 deletions(-) rename kmath-histograms/src/{jvmMain => commonMain}/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt (71%) diff --git a/build.gradle.kts b/build.gradle.kts index 3372d505d..ee77f32df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ allprojects { } group = "space.kscience" - version = "0.3.0-dev-20" + version = "0.3.0-dev-21" } subprojects { diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index 05486d4f6..713f9bcd9 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -5,4 +5,4 @@ kotlin.code.style=official -toolsVersion=0.11.1-kotlin-1.6.10 +toolsVersion=0.11.2-kotlin-1.6.10 diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt index bd5514623..2fac442e7 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt @@ -7,6 +7,7 @@ package space.kscience.kmath.domains import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.indices /** @@ -16,9 +17,17 @@ import space.kscience.kmath.structures.indices * @author Alexander Nozik */ @UnstableKMathAPI -public class HyperSquareDomain(private val lower: Buffer, private val upper: Buffer) : DoubleDomain { +public class HyperSquareDomain(public val lower: Buffer, public val upper: Buffer) : DoubleDomain { + init { + require(lower.size == upper.size) { + "Domain borders size mismatch. Lower borders size is ${lower.size}, but upper borders size is ${upper.size}" + } + } + override val dimension: Int get() = lower.size + public val center: DoubleBuffer get() = DoubleBuffer(dimension) { (lower[it] + upper[it]) / 2.0 } + override operator fun contains(point: Point): Boolean = point.indices.all { i -> point[i] in lower[i]..upper[i] } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt index 61d0b9f33..27f04d60b 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt @@ -5,7 +5,6 @@ package space.kscience.kmath.histogram -import space.kscience.kmath.domains.Domain import space.kscience.kmath.domains.HyperSquareDomain import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.* @@ -13,6 +12,9 @@ import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.structures.* import kotlin.math.floor +/** + * Multivariate histogram space for hyper-square real-field bins. + */ public class DoubleHistogramSpace( private val lower: Buffer, private val upper: Buffer, @@ -47,7 +49,7 @@ public class DoubleHistogramSpace( } @OptIn(UnstableKMathAPI::class) - override fun getDomain(index: IntArray): Domain { + override fun getDomain(index: IntArray): HyperSquareDomain { val lowerBoundary = index.mapIndexed { axis, i -> when (i) { 0 -> Double.NEGATIVE_INFINITY @@ -67,8 +69,13 @@ public class DoubleHistogramSpace( return HyperSquareDomain(lowerBoundary, upperBoundary) } + @OptIn(UnstableKMathAPI::class) + public val Bin.domain: HyperSquareDomain + get() = (this as? DomainBin)?.domain as? HyperSquareDomain + ?: error("Im a teapot. This is not my bin") - override fun produceBin(index: IntArray, value: Double): Bin { + @OptIn(UnstableKMathAPI::class) + override fun produceBin(index: IntArray, value: Double): DomainBin { val domain = getDomain(index) return DomainBin(domain, value) } @@ -96,7 +103,9 @@ public class DoubleHistogramSpace( *) *``` */ - public fun fromRanges(vararg ranges: ClosedFloatingPointRange): DoubleHistogramSpace = DoubleHistogramSpace( + public fun fromRanges( + vararg ranges: ClosedFloatingPointRange, + ): DoubleHistogramSpace = DoubleHistogramSpace( ranges.map(ClosedFloatingPointRange::start).asBuffer(), ranges.map(ClosedFloatingPointRange::endInclusive).asBuffer() ) @@ -110,21 +119,22 @@ public class DoubleHistogramSpace( *) *``` */ - public fun fromRanges(vararg ranges: Pair, Int>): DoubleHistogramSpace = - DoubleHistogramSpace( - ListBuffer( - ranges - .map(Pair, Int>::first) - .map(ClosedFloatingPointRange::start) - ), + public fun fromRanges( + vararg ranges: Pair, Int>, + ): DoubleHistogramSpace = DoubleHistogramSpace( + ListBuffer( + ranges + .map(Pair, Int>::first) + .map(ClosedFloatingPointRange::start) + ), - ListBuffer( - ranges - .map(Pair, Int>::first) - .map(ClosedFloatingPointRange::endInclusive) - ), + ListBuffer( + ranges + .map(Pair, Int>::first) + .map(ClosedFloatingPointRange::endInclusive) + ), - ranges.map(Pair, Int>::second).toIntArray() - ) + ranges.map(Pair, Int>::second).toIntArray() + ) } } \ No newline at end of file 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 9275c1c5e..bfacebb43 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 @@ -7,7 +7,6 @@ package space.kscience.kmath.histogram import space.kscience.kmath.domains.Domain import space.kscience.kmath.linear.Point -import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.DefaultStrides import space.kscience.kmath.nd.FieldND import space.kscience.kmath.nd.Shape @@ -24,22 +23,21 @@ public data class DomainBin>( override val value: Number, ) : Bin, Domain by domain -@OptIn(UnstableKMathAPI::class) public class IndexedHistogram, V : Any>( - public val context: IndexedHistogramSpace, + public val histogramSpace: IndexedHistogramSpace, public val values: StructureND, ) : Histogram> { override fun get(point: Point): Bin? { - val index = context.getIndex(point) ?: return null - return context.produceBin(index, values[index]) + val index = histogramSpace.getIndex(point) ?: return null + return histogramSpace.produceBin(index, values[index]) } - override val dimension: Int get() = context.shape.size + override val dimension: Int get() = histogramSpace.shape.size override val bins: Iterable> - get() = DefaultStrides(context.shape).asSequence().map { - context.produceBin(it, values[it]) + get() = DefaultStrides(histogramSpace.shape).asSequence().map { + histogramSpace.produceBin(it, values[it]) }.asIterable() } @@ -67,13 +65,13 @@ public interface IndexedHistogramSpace, V : Any> public fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram override fun add(left: IndexedHistogram, right: IndexedHistogram): IndexedHistogram { - require(left.context == this) { "Can't operate on a histogram produced by external space" } - require(right.context == this) { "Can't operate on a histogram produced by external space" } + require(left.histogramSpace == this) { "Can't operate on a histogram produced by external space" } + require(right.histogramSpace == this) { "Can't operate on a histogram produced by external space" } return IndexedHistogram(this, histogramValueSpace { left.values + right.values }) } override fun scale(a: IndexedHistogram, value: Double): IndexedHistogram { - require(a.context == this) { "Can't operate on a histogram produced by external space" } + require(a.histogramSpace == this) { "Can't operate on a histogram produced by external space" } return IndexedHistogram(this, histogramValueSpace { a.values * value }) } diff --git a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt similarity index 71% rename from kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt rename to kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt index ac0576a8e..69ea83ae3 100644 --- a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt @@ -37,26 +37,6 @@ public class UnivariateBin( public interface UnivariateHistogram : Histogram { public operator fun get(value: Double): UnivariateBin? override operator fun get(point: Buffer): UnivariateBin? = get(point[0]) - - public companion object { - /** - * Build and fill a [UnivariateHistogram]. Returns a read-only histogram. - */ - public inline fun uniform( - binSize: Double, - start: Double = 0.0, - builder: UnivariateHistogramBuilder.() -> Unit, - ): UnivariateHistogram = TreeHistogramSpace.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: UnivariateHistogramBuilder.() -> Unit, - ): UnivariateHistogram = TreeHistogramSpace.custom(borders).fill(builder) - - } } @UnstableKMathAPI 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 0853615e6..1ab49003c 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 @@ -49,7 +49,9 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> UnivariateDomain fun createBin(value: Double): BinCounter { val binDefinition = binFactory(value) val newBin = BinCounter(binDefinition) - synchronized(this) { bins[binDefinition.center] = newBin } + synchronized(this) { + bins[binDefinition.center] = newBin + } return newBin } @@ -131,6 +133,24 @@ public class TreeHistogramSpace( override val zero: UnivariateHistogram by lazy { fill { } } public companion object { + /** + * Build and fill a [UnivariateHistogram]. Returns a read-only histogram. + */ + public inline fun uniform( + binSize: Double, + start: Double = 0.0, + builder: UnivariateHistogramBuilder.() -> Unit, + ): UnivariateHistogram = 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: UnivariateHistogramBuilder.() -> Unit, + ): UnivariateHistogram = custom(borders).fill(builder) + + /** * Build and fill a [UnivariateHistogram]. Returns a read-only histogram. */ diff --git a/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt b/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt index 28a1b03cb..f1a8f953b 100644 --- a/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt +++ b/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt @@ -13,7 +13,7 @@ class TreeHistogramTest { @Test fun normalFill() { - val histogram = UnivariateHistogram.uniform(0.1) { + val histogram = TreeHistogramSpace.uniform(0.1) { repeat(100_000) { putValue(Random.nextDouble()) } -- 2.34.1 From 29369cd6d7ef4a0b127df5551953e0b7239ffdec Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 22 Mar 2022 22:17:20 +0300 Subject: [PATCH 02/14] [WIP] Another histogram refactor --- .../{UnivariateDomain.kt => Domain1D.kt} | 15 +++- .../kmath/histogram/DoubleHistogramSpace.kt | 22 +++--- .../kscience/kmath/histogram/Histogram.kt | 33 +++++---- .../kscience/kmath/histogram/Histogram1D.kt | 56 +++++++++++++++ .../kmath/histogram/IndexedHistogramSpace.kt | 27 +++---- .../histogram/UniformDoubleHistogram1D.kt | 9 +++ .../kmath/histogram/UnivariateHistogram.kt | 59 --------------- .../kmath/histogram/TreeHistogramSpace.kt | 71 +++++++++---------- 8 files changed, 159 insertions(+), 133 deletions(-) rename kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/{UnivariateDomain.kt => Domain1D.kt} (57%) create mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram1D.kt create mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt delete mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/UnivariateDomain.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt similarity index 57% rename from kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/UnivariateDomain.kt rename to kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt index 9020ef8cb..f50f16c11 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/UnivariateDomain.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/Domain1D.kt @@ -9,16 +9,21 @@ import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI @UnstableKMathAPI -public class UnivariateDomain(public val range: ClosedFloatingPointRange) : DoubleDomain { +public abstract class Domain1D>(public val range: ClosedRange) : Domain { override val dimension: Int get() = 1 - public operator fun contains(d: Double): Boolean = range.contains(d) + public operator fun contains(value: T): Boolean = range.contains(value) - override operator fun contains(point: Point): Boolean { + override operator fun contains(point: Point): Boolean { require(point.size == 0) return contains(point[0]) } +} +@UnstableKMathAPI +public class DoubleDomain1D( + @Suppress("CanBeParameter") public val doubleRange: ClosedFloatingPointRange, +) : Domain1D(doubleRange), DoubleDomain { override fun getLowerBound(num: Int): Double { require(num == 0) return range.start @@ -31,3 +36,7 @@ public class UnivariateDomain(public val range: ClosedFloatingPointRange override fun volume(): Double = range.endInclusive - range.start } + +@UnstableKMathAPI +public val DoubleDomain1D.center: Double + get() = (range.endInclusive + range.start) / 2 diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt index 27f04d60b..c0df8c2cc 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.histogram import space.kscience.kmath.domains.HyperSquareDomain +import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.* import space.kscience.kmath.operations.DoubleField @@ -31,7 +32,7 @@ public class DoubleHistogramSpace( public val dimension: Int get() = lower.size override val shape: IntArray = IntArray(binNums.size) { binNums[it] + 2 } - override val histogramValueSpace: DoubleFieldND = DoubleField.ndAlgebra(*shape) + override val histogramValueAlgebra: DoubleFieldND = DoubleField.ndAlgebra(*shape) private val binSize = DoubleBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] } @@ -70,21 +71,20 @@ public class DoubleHistogramSpace( } @OptIn(UnstableKMathAPI::class) - public val Bin.domain: HyperSquareDomain - get() = (this as? DomainBin)?.domain as? HyperSquareDomain - ?: error("Im a teapot. This is not my bin") - - @OptIn(UnstableKMathAPI::class) - override fun produceBin(index: IntArray, value: Double): DomainBin { + override fun produceBin(index: IntArray, value: Double): DomainBin { val domain = getDomain(index) return DomainBin(domain, value) } - override fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram { + override fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram { val ndCounter = StructureND.auto(shape) { Counter.double() } - val hBuilder = HistogramBuilder { point, value -> - val index = getIndex(point) - ndCounter[index].add(value.toDouble()) + val hBuilder = object : HistogramBuilder { + override val defaultValue: Double get() = 1.0 + + override fun putValue(point: Point, value: Double) { + val index = getIndex(point) + ndCounter[index].add(value) + } } hBuilder.apply(builder) val values: BufferND = ndCounter.mapToBuffer { it.value } 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 4e803fc63..64c031c7a 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 @@ -13,14 +13,14 @@ import space.kscience.kmath.structures.asBuffer /** * The binned data element. Could be a histogram bin with a number of counts or an artificial construct. */ -public interface Bin : Domain { +public interface Bin : Domain { /** * The value of this bin. */ - public val value: Number + public val value: V } -public interface Histogram> { +public interface Histogram> { /** * Find existing bin, corresponding to given coordinates */ @@ -32,29 +32,38 @@ public interface Histogram> { public val dimension: Int public val bins: Iterable + + public companion object { + //A discoverability root + } } -public fun interface HistogramBuilder { +public interface HistogramBuilder { /** - * Increment appropriate bin + * The default value increment for a bin */ - public fun putValue(point: Point, value: Number) + public val defaultValue: V + + /** + * Increment appropriate bin with given value + */ + public fun putValue(point: Point, value: V = defaultValue) } -public fun > HistogramBuilder.put(point: Point): Unit = putValue(point, 1.0) +public fun HistogramBuilder.put(point: Point): Unit = putValue(point) -public fun HistogramBuilder.put(vararg point: T): Unit = put(point.asBuffer()) +public fun HistogramBuilder.put(vararg point: T): Unit = put(point.asBuffer()) -public fun HistogramBuilder.put(vararg point: Number): Unit = +public fun HistogramBuilder.put(vararg point: Number): Unit = put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray())) -public fun HistogramBuilder.put(vararg point: Double): Unit = put(DoubleBuffer(point)) -public fun HistogramBuilder.fill(sequence: Iterable>): Unit = sequence.forEach { put(it) } +public fun HistogramBuilder.put(vararg point: Double): Unit = put(DoubleBuffer(point)) +public fun HistogramBuilder.fill(sequence: Iterable>): Unit = sequence.forEach { put(it) } /** * Pass a sequence builder into histogram */ -public fun HistogramBuilder.fill(block: suspend SequenceScope>.() -> Unit): Unit = +public fun HistogramBuilder.fill(block: suspend SequenceScope>.() -> Unit): Unit = fill(sequence(block).asIterable()) 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 new file mode 100644 index 000000000..e9c62b141 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Histogram1D.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.histogram + +import space.kscience.kmath.domains.Domain1D +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.asSequence +import space.kscience.kmath.structures.Buffer + + +/** + * A univariate bin based on a range + * + * @property value 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, +) : Bin, ClosedRange by domain.range { + + override val dimension: Int get() = 1 + + override fun contains(point: Buffer): Boolean = point.size == 1 && contains(point[0]) +} + +@OptIn(UnstableKMathAPI::class) +public interface Histogram1D, V> : Histogram> { + override val dimension: Int get() = 1 + public operator fun get(value: T): Bin1D? + override operator fun get(point: Buffer): Bin1D? = get(point[0]) +} + +@UnstableKMathAPI +public interface Histogram1DBuilder : HistogramBuilder { + /** + * Thread safe put operation + */ + public fun putValue(at: T, value: V = defaultValue) +} + +@UnstableKMathAPI +public fun Histogram1DBuilder.fill(items: Iterable): Unit = + items.forEach(this::putValue) + +@UnstableKMathAPI +public fun Histogram1DBuilder.fill(array: DoubleArray): Unit = + array.forEach(this::putValue) + +@UnstableKMathAPI +public fun Histogram1DBuilder.fill(buffer: Buffer): Unit = + buffer.asSequence().forEach(this::putValue) \ No newline at end of file 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 bfacebb43..a2623b1a1 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 @@ -18,24 +18,28 @@ import space.kscience.kmath.operations.invoke /** * A simple histogram bin based on domain */ -public data class DomainBin>( +public data class DomainBin, out V>( public val domain: Domain, - override val value: Number, -) : Bin, Domain by domain + override val value: V, +) : Bin, Domain by domain +/** + * @param T the type of the argument space + * @param V the type of bin value + */ public class IndexedHistogram, V : Any>( public val histogramSpace: IndexedHistogramSpace, public val values: StructureND, -) : Histogram> { +) : Histogram> { - override fun get(point: Point): Bin? { + override fun get(point: Point): DomainBin? { val index = histogramSpace.getIndex(point) ?: return null return histogramSpace.produceBin(index, values[index]) } override val dimension: Int get() = histogramSpace.shape.size - override val bins: Iterable> + override val bins: Iterable> get() = DefaultStrides(histogramSpace.shape).asSequence().map { histogramSpace.produceBin(it, values[it]) }.asIterable() @@ -46,9 +50,8 @@ public class IndexedHistogram, V : Any>( */ public interface IndexedHistogramSpace, V : Any> : Group>, ScaleOperations> { - //public val valueSpace: Space public val shape: Shape - public val histogramValueSpace: FieldND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), + public val histogramValueAlgebra: FieldND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), /** * Resolve index of the bin including given [point] @@ -60,19 +63,19 @@ public interface IndexedHistogramSpace, V : Any> */ public fun getDomain(index: IntArray): Domain? - public fun produceBin(index: IntArray, value: V): Bin + public fun produceBin(index: IntArray, value: V): DomainBin - public fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram + public fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram override fun add(left: IndexedHistogram, right: IndexedHistogram): IndexedHistogram { require(left.histogramSpace == this) { "Can't operate on a histogram produced by external space" } require(right.histogramSpace == this) { "Can't operate on a histogram produced by external space" } - return IndexedHistogram(this, histogramValueSpace { left.values + right.values }) + return IndexedHistogram(this, histogramValueAlgebra { left.values + right.values }) } override fun scale(a: IndexedHistogram, value: Double): IndexedHistogram { require(a.histogramSpace == this) { "Can't operate on a histogram produced by external space" } - return IndexedHistogram(this, histogramValueSpace { a.values * value }) + return IndexedHistogram(this, histogramValueAlgebra { a.values * value }) } override val zero: IndexedHistogram get() = produce { } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt new file mode 100644 index 000000000..856cd8592 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.histogram + +//class UniformDoubleHistogram1D: DoubleHistogram1D { +//} \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt deleted file mode 100644 index 69ea83ae3..000000000 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.histogram - -import space.kscience.kmath.domains.UnivariateDomain -import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.asSequence -import space.kscience.kmath.structures.Buffer - - -@UnstableKMathAPI -public val UnivariateDomain.center: Double - get() = (range.endInclusive + range.start) / 2 - -/** - * A univariate bin based on a range - * - * @property value The value of histogram including weighting - * @property standardDeviation Standard deviation of the bin value. Zero or negative if not applicable - */ -@UnstableKMathAPI -public class UnivariateBin( - public val domain: UnivariateDomain, - override val value: Double, - public val standardDeviation: Double, -) : Bin, ClosedFloatingPointRange by domain.range { - - override val dimension: Int get() = 1 - - override fun contains(point: Buffer): Boolean = point.size == 1 && contains(point[0]) -} - -@OptIn(UnstableKMathAPI::class) -public interface UnivariateHistogram : Histogram { - public operator fun get(value: Double): UnivariateBin? - override operator fun get(point: Buffer): UnivariateBin? = get(point[0]) -} - -@UnstableKMathAPI -public interface UnivariateHistogramBuilder : HistogramBuilder { - /** - * Thread safe put operation - */ - public fun putValue(at: Double, value: Double = 1.0) - - override fun putValue(point: Buffer, value: Number) -} - -@UnstableKMathAPI -public fun UnivariateHistogramBuilder.fill(items: Iterable): Unit = items.forEach(this::putValue) - -@UnstableKMathAPI -public fun UnivariateHistogramBuilder.fill(array: DoubleArray): Unit = array.forEach(this::putValue) - -@UnstableKMathAPI -public fun UnivariateHistogramBuilder.fill(buffer: Buffer): Unit = buffer.asSequence().forEach(this::putValue) \ 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 1ab49003c..9d07d5014 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 @@ -5,7 +5,7 @@ package space.kscience.kmath.histogram -import space.kscience.kmath.domains.UnivariateDomain +import space.kscience.kmath.domains.DoubleDomain1D import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Group import space.kscience.kmath.operations.ScaleOperations @@ -15,7 +15,7 @@ import kotlin.math.abs import kotlin.math.floor import kotlin.math.sqrt -private fun > TreeMap.getBin(value: Double): B? { +private fun > TreeMap.getBin(value: Double): B? { // check ceiling entry and return it if it is what needed val ceil = ceilingEntry(value)?.value if (ceil != null && value in ceil) return ceil @@ -28,19 +28,18 @@ private fun > TreeMap.getBin(val @UnstableKMathAPI public class TreeHistogram( - private val binMap: TreeMap, -) : UnivariateHistogram { - override fun get(value: Double): UnivariateBin? = binMap.getBin(value) - override val dimension: Int get() = 1 - override val bins: Collection get() = binMap.values + private val binMap: TreeMap>, +) : Histogram1D { + override fun get(value: Double): Bin1D? = binMap.getBin(value) + override val bins: Collection> get() = binMap.values } @OptIn(UnstableKMathAPI::class) @PublishedApi -internal class TreeHistogramBuilder(val binFactory: (Double) -> UnivariateDomain) : UnivariateHistogramBuilder { +internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) : Histogram1DBuilder { - internal class BinCounter(val domain: UnivariateDomain, val counter: Counter = Counter.double()) : - ClosedFloatingPointRange by domain.range + internal class BinCounter(val domain: DoubleDomain1D, val counter: Counter = Counter.double()) : + ClosedRange by domain.range private val bins: TreeMap = TreeMap() @@ -64,15 +63,15 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> UnivariateDomain } } - override fun putValue(point: Buffer, value: Number) { + override fun putValue(point: Buffer, value: Double) { require(point.size == 1) { "Only points with single value could be used in univariate histogram" } putValue(point[0], value.toDouble()) } fun build(): TreeHistogram { - val map = bins.mapValuesTo(TreeMap()) { (_, binCounter) -> + val map = bins.mapValuesTo(TreeMap>()) { (_, binCounter) -> val count = binCounter.counter.value - UnivariateBin(binCounter.domain, count, sqrt(count)) + Bin1D(binCounter.domain, count, sqrt(count)) } return TreeHistogram(map) } @@ -83,23 +82,23 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> UnivariateDomain */ @UnstableKMathAPI public class TreeHistogramSpace( - @PublishedApi internal val binFactory: (Double) -> UnivariateDomain, -) : Group, ScaleOperations { + @PublishedApi internal val binFactory: (Double) -> DoubleDomain1D, +) : Group>, ScaleOperations> { - public inline fun fill(block: UnivariateHistogramBuilder.() -> Unit): UnivariateHistogram = + public inline fun fill(block: Histogram1DBuilder.() -> Unit): Histogram1D = TreeHistogramBuilder(binFactory).apply(block).build() override fun add( - left: UnivariateHistogram, - right: UnivariateHistogram, - ): UnivariateHistogram { + left: Histogram1D, + right: Histogram1D, + ): Histogram1D { // 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, - UnivariateBin( + Bin1D( def, value = (left[def.center]?.value ?: 0.0) + (right[def.center]?.value ?: 0.0), standardDeviation = (left[def.center]?.standardDeviation @@ -111,12 +110,12 @@ public class TreeHistogramSpace( return TreeHistogram(bins) } - override fun scale(a: UnivariateHistogram, value: Double): UnivariateHistogram { - val bins = TreeMap().apply { + override fun scale(a: Histogram1D, value: Double): Histogram1D { + val bins = TreeMap>().apply { a.bins.forEach { bin -> put( bin.domain.center, - UnivariateBin( + Bin1D( bin.domain, value = bin.value * value, standardDeviation = abs(bin.standardDeviation * value) @@ -128,38 +127,38 @@ public class TreeHistogramSpace( return TreeHistogram(bins) } - override fun UnivariateHistogram.unaryMinus(): UnivariateHistogram = this * (-1) + override fun Histogram1D.unaryMinus(): Histogram1D = this * (-1) - override val zero: UnivariateHistogram by lazy { fill { } } + override val zero: Histogram1D by lazy { fill { } } public companion object { /** - * Build and fill a [UnivariateHistogram]. Returns a read-only histogram. + * Build and fill a [DoubleHistogram1D]. Returns a read-only histogram. */ public inline fun uniform( binSize: Double, start: Double = 0.0, - builder: UnivariateHistogramBuilder.() -> Unit, - ): UnivariateHistogram = uniform(binSize, start).fill(builder) + builder: Histogram1DBuilder.() -> Unit, + ): Histogram1D = 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: UnivariateHistogramBuilder.() -> Unit, - ): UnivariateHistogram = custom(borders).fill(builder) + builder: Histogram1DBuilder.() -> Unit, + ): Histogram1D = custom(borders).fill(builder) /** - * Build and fill a [UnivariateHistogram]. Returns a read-only histogram. + * Build and fill a [DoubleHistogram1D]. Returns a read-only histogram. */ public fun uniform( binSize: Double, start: Double = 0.0, ): TreeHistogramSpace = TreeHistogramSpace { value -> val center = start + binSize * floor((value - start) / binSize + 0.5) - UnivariateDomain((center - binSize / 2)..(center + binSize / 2)) + DoubleDomain1D((center - binSize / 2)..(center + binSize / 2)) } /** @@ -170,11 +169,11 @@ public class TreeHistogramSpace( return TreeHistogramSpace { value -> when { - value < sorted.first() -> UnivariateDomain( + value < sorted.first() -> DoubleDomain1D( Double.NEGATIVE_INFINITY..sorted.first() ) - value > sorted.last() -> UnivariateDomain( + value > sorted.last() -> DoubleDomain1D( sorted.last()..Double.POSITIVE_INFINITY ) @@ -182,7 +181,7 @@ public class TreeHistogramSpace( val index = sorted.indices.first { value > sorted[it] } val left = sorted[index] val right = sorted[index + 1] - UnivariateDomain(left..right) + DoubleDomain1D(left..right) } } } -- 2.34.1 From 3a3a5bd77f02b8a1fc43c399c53a5086a7891d5c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 23 Mar 2022 14:07:24 +0300 Subject: [PATCH 03/14] 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) /** -- 2.34.1 From ce82d2d07633240f8df932fb9b8ac185e0e8c9d6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 23 Mar 2022 15:51:08 +0300 Subject: [PATCH 04/14] Histogram API refactor --- .../{UniformDoubleHistogram1D.kt => UniformHistogram1D.kt} | 5 ++++- .../space/kscience/kmath/histogram/TreeHistogramSpace.kt | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) rename kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/{UniformDoubleHistogram1D.kt => UniformHistogram1D.kt} (58%) diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt similarity index 58% rename from kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt rename to kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index 856cd8592..ed8b2e29d 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformDoubleHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -5,5 +5,8 @@ package space.kscience.kmath.histogram -//class UniformDoubleHistogram1D: DoubleHistogram1D { +//class UniformHistogram1D( +// public val borders: Buffer, +// public val values: Buffer, +//) : Histogram1D { //} \ 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 38e533db0..e85bb0a3c 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 @@ -3,6 +3,8 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:OptIn(UnstableKMathAPI::class) + package space.kscience.kmath.histogram import space.kscience.kmath.domains.DoubleDomain1D @@ -31,7 +33,6 @@ public data class ValueAndError(val value: Double, val error: Double) public typealias WeightedBin1D = Bin1D -@UnstableKMathAPI public class TreeHistogram( private val binMap: TreeMap, ) : Histogram1D { @@ -39,7 +40,6 @@ public class TreeHistogram( override val bins: Collection get() = binMap.values } -@OptIn(UnstableKMathAPI::class) @PublishedApi internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) : Histogram1DBuilder { @@ -87,7 +87,6 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) /** * A space for univariate histograms with variable bin borders based on a tree map */ -@UnstableKMathAPI public class TreeHistogramSpace( @PublishedApi internal val binFactory: (Double) -> DoubleDomain1D, ) : Group, ScaleOperations { -- 2.34.1 From 73f72f12bca21554764e2b24dcb5fd4e22a64983 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 5 Apr 2022 23:23:29 +0300 Subject: [PATCH 05/14] [WIP] Another histogram refactor --- CHANGELOG.md | 1 + .../space/kscience/kmath/domains/Domain1D.kt | 17 ++++ .../space/kscience/kmath/misc/sorting.kt | 75 +++++++++++---- .../space/kscience/kmath/misc/PermSortTest.kt | 25 +++-- kmath-for-real/build.gradle.kts | 15 +-- kmath-histograms/build.gradle.kts | 2 + ...togramSpace.kt => DoubleHistogramGroup.kt} | 8 +- .../kscience/kmath/histogram/Histogram1D.kt | 10 +- ...ogramSpace.kt => IndexedHistogramGroup.kt} | 6 +- .../kmath/histogram/UniformHistogram1D.kt | 94 ++++++++++++++++++- .../histogram/MultivariateHistogramTest.kt | 6 +- .../kmath/histogram/UniformHistogram1DTest.kt | 34 +++++++ .../kmath/distributions/Distribution.kt | 4 +- .../kmath/distributions/NormalDistribution.kt | 15 +-- .../space/kscience/kmath/stat/Sampler.kt | 9 ++ .../kmath/stat/UniformDistribution.kt | 4 +- 16 files changed, 258 insertions(+), 67 deletions(-) rename kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/{DoubleHistogramSpace.kt => DoubleHistogramGroup.kt} (96%) rename kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/{IndexedHistogramSpace.kt => IndexedHistogramGroup.kt} (93%) create mode 100644 kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index a19b1f467..d710c330b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - Operations -> Ops - Default Buffer and ND algebras are now Ops and lack neutral elements (0, 1) as well as algebra-level shapes. - Tensor algebra takes read-only structures as input and inherits AlgebraND +- `UnivariateDistribution` renamed to `Distribution1D` ### Deprecated - Specialized `DoubleBufferAlgebra` 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 ccd1c3edb..3d531349c 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 @@ -35,6 +35,23 @@ public class DoubleDomain1D( } override fun volume(): Double = range.endInclusive - range.start + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as DoubleDomain1D + + if (doubleRange != other.doubleRange) return false + + return true + } + + override fun hashCode(): Int = doubleRange.hashCode() + + override fun toString(): String = doubleRange.toString() + + } @UnstableKMathAPI diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/sorting.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/sorting.kt index a144e49b4..dc5421136 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/sorting.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/sorting.kt @@ -3,43 +3,71 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ + package space.kscience.kmath.misc -import kotlin.comparisons.* import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.VirtualBuffer /** - * Return a new list filled with buffer indices. Indice order is defined by sorting associated buffer value. - * This feature allows to sort buffer values without reordering its content. + * Return a new array filled with buffer indices. Indices order is defined by sorting associated buffer value. + * This feature allows sorting buffer values without reordering its content. * - * @return List of buffer indices, sorted by associated value. + * @return Buffer indices, sorted by associated value. */ -@PerformancePitfall @UnstableKMathAPI -public fun > Buffer.permSort() : IntArray = _permSortWith(compareBy { get(it) }) +public fun > Buffer.indicesSorted(): IntArray = permSortIndicesWith(compareBy { get(it) }) + +/** + * Create a zero-copy virtual buffer that contains the same elements but in ascending order + */ +@OptIn(UnstableKMathAPI::class) +public fun > Buffer.sorted(): Buffer { + val permutations = indicesSorted() + return VirtualBuffer(size) { this[permutations[it]] } +} -@PerformancePitfall @UnstableKMathAPI -public fun > Buffer.permSortDescending() : IntArray = _permSortWith(compareByDescending { get(it) }) +public fun > Buffer.indicesSortedDescending(): IntArray = + permSortIndicesWith(compareByDescending { get(it) }) + +/** + * Create a zero-copy virtual buffer that contains the same elements but in descending order + */ +@OptIn(UnstableKMathAPI::class) +public fun > Buffer.sortedDescending(): Buffer { + val permutations = indicesSortedDescending() + return VirtualBuffer(size) { this[permutations[it]] } +} -@PerformancePitfall @UnstableKMathAPI -public fun > Buffer.permSortBy(selector: (V) -> C) : IntArray = _permSortWith(compareBy { selector(get(it)) }) +public fun > Buffer.indicesSortedBy(selector: (V) -> C): IntArray = + permSortIndicesWith(compareBy { selector(get(it)) }) + +@OptIn(UnstableKMathAPI::class) +public fun > Buffer.sortedBy(selector: (V) -> C): Buffer { + val permutations = indicesSortedBy(selector) + return VirtualBuffer(size) { this[permutations[it]] } +} -@PerformancePitfall @UnstableKMathAPI -public fun > Buffer.permSortByDescending(selector: (V) -> C) : IntArray = _permSortWith(compareByDescending { selector(get(it)) }) +public fun > Buffer.indicesSortedByDescending(selector: (V) -> C): IntArray = + permSortIndicesWith(compareByDescending { selector(get(it)) }) + +@OptIn(UnstableKMathAPI::class) +public fun > Buffer.sortedByDescending(selector: (V) -> C): Buffer { + val permutations = indicesSortedByDescending(selector) + return VirtualBuffer(size) { this[permutations[it]] } +} -@PerformancePitfall @UnstableKMathAPI -public fun Buffer.permSortWith(comparator : Comparator) : IntArray = _permSortWith { i1, i2 -> comparator.compare(get(i1), get(i2)) } +public fun Buffer.indicesSortedWith(comparator: Comparator): IntArray = + permSortIndicesWith { i1, i2 -> comparator.compare(get(i1), get(i2)) } -@PerformancePitfall -@UnstableKMathAPI -private fun Buffer._permSortWith(comparator : Comparator) : IntArray { - if (size < 2) return IntArray(size) +private fun Buffer.permSortIndicesWith(comparator: Comparator): IntArray { + if (size < 2) return IntArray(size) { 0 } - /* TODO: optimisation : keep a constant big array of indices (Ex: from 0 to 4096), then create indice + /* TODO: optimisation : keep a constant big array of indices (Ex: from 0 to 4096), then create indices * arrays more efficiently by copying subpart of cached one. For bigger needs, we could copy entire * cached array, then fill remaining indices manually. Not done for now, because: * 1. doing it right would require some statistics about common used buffer sizes. @@ -53,3 +81,12 @@ private fun Buffer._permSortWith(comparator : Comparator) : IntArray */ return packedIndices.sortedWith(comparator).toIntArray() } + +/** + * Checks that the [Buffer] is sorted (ascending) and throws [IllegalArgumentException] if it is not. + */ +public fun > Buffer.requireSorted() { + for (i in 0..(size - 2)) { + require(get(i + 1) >= get(i)) { "The buffer is not sorted at index $i" } + } +} diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/misc/PermSortTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/misc/PermSortTest.kt index 0a2bb9138..4a724ac5f 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/misc/PermSortTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/misc/PermSortTest.kt @@ -6,14 +6,13 @@ package space.kscience.kmath.misc import space.kscience.kmath.misc.PermSortTest.Platform.* -import kotlin.random.Random -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - import space.kscience.kmath.structures.IntBuffer import space.kscience.kmath.structures.asBuffer +import kotlin.random.Random +import kotlin.test.Test import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue class PermSortTest { @@ -29,9 +28,9 @@ class PermSortTest { @Test fun testOnEmptyBuffer() { val emptyBuffer = IntBuffer(0) {it} - var permutations = emptyBuffer.permSort() + var permutations = emptyBuffer.indicesSorted() assertTrue(permutations.isEmpty(), "permutation on an empty buffer should return an empty result") - permutations = emptyBuffer.permSortDescending() + permutations = emptyBuffer.indicesSortedDescending() assertTrue(permutations.isEmpty(), "permutation on an empty buffer should return an empty result") } @@ -47,25 +46,25 @@ class PermSortTest { @Test fun testPermSortBy() { - val permutations = platforms.permSortBy { it.name } + val permutations = platforms.indicesSortedBy { it.name } val expected = listOf(ANDROID, JS, JVM, NATIVE, WASM) assertContentEquals(expected, permutations.map { platforms[it] }, "Ascending PermSort by name") } @Test fun testPermSortByDescending() { - val permutations = platforms.permSortByDescending { it.name } + val permutations = platforms.indicesSortedByDescending { it.name } val expected = listOf(WASM, NATIVE, JVM, JS, ANDROID) assertContentEquals(expected, permutations.map { platforms[it] }, "Descending PermSort by name") } @Test fun testPermSortWith() { - var permutations = platforms.permSortWith { p1, p2 -> p1.name.length.compareTo(p2.name.length) } + var permutations = platforms.indicesSortedWith { p1, p2 -> p1.name.length.compareTo(p2.name.length) } val expected = listOf(JS, JVM, WASM, NATIVE, ANDROID) assertContentEquals(expected, permutations.map { platforms[it] }, "PermSort using custom ascending comparator") - permutations = platforms.permSortWith(compareByDescending { it.name.length }) + permutations = platforms.indicesSortedWith(compareByDescending { it.name.length }) assertContentEquals(expected.reversed(), permutations.map { platforms[it] }, "PermSort using custom descending comparator") } @@ -75,7 +74,7 @@ class PermSortTest { println("Test randomization seed: $seed") val buffer = Random(seed).buffer(bufferSize) - val indices = buffer.permSort() + val indices = buffer.indicesSorted() assertEquals(bufferSize, indices.size) // Ensure no doublon is present in indices @@ -87,7 +86,7 @@ class PermSortTest { assertTrue(current <= next, "Permutation indices not properly sorted") } - val descIndices = buffer.permSortDescending() + val descIndices = buffer.indicesSortedDescending() assertEquals(bufferSize, descIndices.size) // Ensure no doublon is present in indices assertEquals(descIndices.toSet().size, descIndices.size) diff --git a/kmath-for-real/build.gradle.kts b/kmath-for-real/build.gradle.kts index 4cccaef5c..18c2c50ad 100644 --- a/kmath-for-real/build.gradle.kts +++ b/kmath-for-real/build.gradle.kts @@ -21,19 +21,22 @@ readme { feature( id = "DoubleVector", - description = "Numpy-like operations for Buffers/Points", ref = "src/commonMain/kotlin/space/kscience/kmath/real/DoubleVector.kt" - ) + ){ + "Numpy-like operations for Buffers/Points" + } feature( id = "DoubleMatrix", - description = "Numpy-like operations for 2d real structures", ref = "src/commonMain/kotlin/space/kscience/kmath/real/DoubleMatrix.kt" - ) + ){ + "Numpy-like operations for 2d real structures" + } feature( id = "grids", - description = "Uniform grid generators", ref = "src/commonMain/kotlin/space/kscience/kmath/structures/grids.kt" - ) + ){ + "Uniform grid generators" + } } diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 7e511faa0..51271bb0a 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -17,6 +17,8 @@ kotlin.sourceSets { commonTest { dependencies { implementation(project(":kmath-for-real")) + implementation(projects.kmath.kmathStat) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0") } } } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt similarity index 96% rename from kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt rename to kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt index c0df8c2cc..a80ed119c 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramSpace.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt @@ -16,11 +16,11 @@ import kotlin.math.floor /** * Multivariate histogram space for hyper-square real-field bins. */ -public class DoubleHistogramSpace( +public class DoubleHistogramGroup( private val lower: Buffer, private val upper: Buffer, private val binNums: IntArray = IntArray(lower.size) { 20 }, -) : IndexedHistogramSpace { +) : IndexedHistogramGroup { init { // argument checks @@ -105,7 +105,7 @@ public class DoubleHistogramSpace( */ public fun fromRanges( vararg ranges: ClosedFloatingPointRange, - ): DoubleHistogramSpace = DoubleHistogramSpace( + ): DoubleHistogramGroup = DoubleHistogramGroup( ranges.map(ClosedFloatingPointRange::start).asBuffer(), ranges.map(ClosedFloatingPointRange::endInclusive).asBuffer() ) @@ -121,7 +121,7 @@ public class DoubleHistogramSpace( */ public fun fromRanges( vararg ranges: Pair, Int>, - ): DoubleHistogramSpace = DoubleHistogramSpace( + ): DoubleHistogramGroup = DoubleHistogramGroup( ListBuffer( ranges .map(Pair, Int>::first) 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 4f193f943..237bd87b9 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 @@ -6,6 +6,7 @@ package space.kscience.kmath.histogram import space.kscience.kmath.domains.Domain1D +import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.asSequence import space.kscience.kmath.structures.Buffer @@ -18,7 +19,7 @@ import space.kscience.kmath.structures.Buffer * @property standardDeviation Standard deviation of the bin value. Zero or negative if not applicable */ @UnstableKMathAPI -public class Bin1D, out V>( +public data class Bin1D, out V>( public val domain: Domain1D, override val binValue: V, ) : Bin, ClosedRange by domain.range { @@ -35,12 +36,15 @@ public interface Histogram1D, V> : Histogram override operator fun get(point: Buffer): Bin1D? = get(point[0]) } -@UnstableKMathAPI public interface Histogram1DBuilder : HistogramBuilder { /** * Thread safe put operation */ public fun putValue(at: T, value: V = defaultValue) + + override fun putValue(point: Point, value: V) { + putValue(point[0], value) + } } @UnstableKMathAPI @@ -52,5 +56,5 @@ public fun Histogram1DBuilder.fill(array: DoubleArray): Unit = array.forEach(this::putValue) @UnstableKMathAPI -public fun Histogram1DBuilder.fill(buffer: Buffer): Unit = +public fun Histogram1DBuilder.fill(buffer: Buffer): Unit = buffer.asSequence().forEach(this::putValue) \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt similarity index 93% rename from kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt rename to kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt index 3e4b07984..9749f8403 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt @@ -28,7 +28,7 @@ public data class DomainBin, out V>( * @param V the type of bin value */ public class IndexedHistogram, V : Any>( - public val histogramSpace: IndexedHistogramSpace, + public val histogramSpace: IndexedHistogramGroup, public val values: StructureND, ) : Histogram> { @@ -48,8 +48,8 @@ public class IndexedHistogram, V : Any>( /** * A space for producing histograms with values in a NDStructure */ -public interface IndexedHistogramSpace, V : Any> - : Group>, ScaleOperations> { +public interface IndexedHistogramGroup, V : Any> : Group>, + ScaleOperations> { public val shape: Shape public val histogramValueAlgebra: FieldND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index ed8b2e29d..44638c29b 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -5,8 +5,92 @@ package space.kscience.kmath.histogram -//class UniformHistogram1D( -// public val borders: Buffer, -// public val values: Buffer, -//) : Histogram1D { -//} \ No newline at end of file +import space.kscience.kmath.domains.DoubleDomain1D +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.Group +import space.kscience.kmath.operations.Ring +import space.kscience.kmath.operations.ScaleOperations +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.Buffer +import kotlin.math.floor + +@OptIn(UnstableKMathAPI::class) +public class UniformHistogram1D( + public val group: UniformHistogram1DGroup, + public val values: Map, +) : Histogram1D { + + private val startPoint get() = group.startPoint + private val binSize get() = group.binSize + + private fun produceBin(index: Int, value: V): Bin1D { + val domain = DoubleDomain1D((startPoint + index * binSize)..(startPoint + (index + 1) * binSize)) + return Bin1D(domain, value) + } + + override val bins: Iterable> get() = values.map { produceBin(it.key, it.value) } + + override fun get(value: Double): Bin1D? { + val index: Int = group.getIndex(value) + val v = values[index] + return v?.let { produceBin(index, it) } + } +} + +public class UniformHistogram1DGroup( + public val valueAlgebra: A, + public val binSize: Double, + public val startPoint: Double = 0.0, +) : Group>, ScaleOperations> where A : Ring, A : ScaleOperations { + override val zero: UniformHistogram1D by lazy { UniformHistogram1D(this, emptyMap()) } + + public fun getIndex(at: Double): Int = floor((at - startPoint) / binSize).toInt() + + override fun add(left: UniformHistogram1D, right: UniformHistogram1D): UniformHistogram1D = valueAlgebra { + require(left.group == this@UniformHistogram1DGroup) + require(right.group == this@UniformHistogram1DGroup) + val keys = left.values.keys + right.values.keys + UniformHistogram1D( + this@UniformHistogram1DGroup, + keys.associateWith { (left.values[it] ?: valueAlgebra.zero) + (right.values[it] ?: valueAlgebra.zero) } + ) + } + + override fun UniformHistogram1D.unaryMinus(): UniformHistogram1D = valueAlgebra { + UniformHistogram1D(this@UniformHistogram1DGroup, values.mapValues { -it.value }) + } + + override fun scale( + a: UniformHistogram1D, + value: Double, + ): UniformHistogram1D = UniformHistogram1D( + this@UniformHistogram1DGroup, + a.values.mapValues { valueAlgebra.scale(it.value, value) } + ) + + public inline fun produce(block: Histogram1DBuilder.() -> Unit): UniformHistogram1D { + val map = HashMap() + val builder = object : Histogram1DBuilder { + override val defaultValue: V get() = valueAlgebra.zero + + override fun putValue(at: Double, value: V) { + val index = getIndex(at) + map[index] = with(valueAlgebra) { (map[index] ?: zero) + one } + } + } + builder.block() + return UniformHistogram1D(this, map) + } +} + +public fun Histogram.Companion.uniform1D( + algebra: A, + binSize: Double, + startPoint: Double = 0.0, +): UniformHistogram1DGroup where A : Ring, A : ScaleOperations = + UniformHistogram1DGroup(algebra, binSize, startPoint) + +@UnstableKMathAPI +public fun UniformHistogram1DGroup.produce( + buffer: Buffer, +): UniformHistogram1D = produce { fill(buffer) } \ No newline at end of file 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 31a29676c..1426b35fa 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 @@ -14,7 +14,7 @@ import kotlin.test.* internal class MultivariateHistogramTest { @Test fun testSinglePutHistogram() { - val hSpace = DoubleHistogramSpace.fromRanges( + val hSpace = DoubleHistogramGroup.fromRanges( (-1.0..1.0), (-1.0..1.0) ) @@ -29,7 +29,7 @@ internal class MultivariateHistogramTest { @Test fun testSequentialPut() { - val hSpace = DoubleHistogramSpace.fromRanges( + val hSpace = DoubleHistogramGroup.fromRanges( (-1.0..1.0), (-1.0..1.0), (-1.0..1.0) @@ -49,7 +49,7 @@ internal class MultivariateHistogramTest { @Test fun testHistogramAlgebra() { - DoubleHistogramSpace.fromRanges( + DoubleHistogramGroup.fromRanges( (-1.0..1.0), (-1.0..1.0), (-1.0..1.0) diff --git a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt new file mode 100644 index 000000000..d23afdb8b --- /dev/null +++ b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.histogram + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import space.kscience.kmath.distributions.NormalDistribution +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.stat.RandomGenerator +import space.kscience.kmath.stat.nextBuffer +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(ExperimentalCoroutinesApi::class, UnstableKMathAPI::class) +internal class UniformHistogram1DTest { + @Test + fun normal() = runTest { + val generator = RandomGenerator.default(123) + val distribution = NormalDistribution(0.0, 1.0) + with(Histogram.uniform1D(DoubleField, 0.1)) { + val h1 = produce(distribution.nextBuffer(generator, 10000)) + + val h2 = produce(distribution.nextBuffer(generator, 50000)) + + val h3 = h1 + h2 + + assertEquals(60000, h3.bins.sumOf { it.binValue }.toInt()) + } + } +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/Distribution.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/Distribution.kt index 3d3f95f8f..8dbcb3367 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/Distribution.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/Distribution.kt @@ -27,7 +27,7 @@ public interface Distribution : Sampler { public companion object } -public interface UnivariateDistribution> : Distribution { +public interface Distribution1D> : Distribution { /** * Cumulative distribution for ordered parameter (CDF) */ @@ -37,7 +37,7 @@ public interface UnivariateDistribution> : Distribution { /** * Compute probability integral in an interval */ -public fun > UnivariateDistribution.integral(from: T, to: T): Double { +public fun > Distribution1D.integral(from: T, to: T): Double { require(to > from) return cumulative(to) - cumulative(from) } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/NormalDistribution.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/NormalDistribution.kt index 66e041f05..cfc6eba04 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/NormalDistribution.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/distributions/NormalDistribution.kt @@ -14,14 +14,9 @@ import space.kscience.kmath.stat.RandomGenerator import kotlin.math.* /** - * Implements [UnivariateDistribution] for the normal (gaussian) distribution. + * Implements [Distribution1D] for the normal (gaussian) distribution. */ -public class NormalDistribution(public val sampler: GaussianSampler) : UnivariateDistribution { - public constructor( - mean: Double, - standardDeviation: Double, - normalized: NormalizedGaussianSampler = ZigguratNormalizedGaussianSampler, - ) : this(GaussianSampler(mean, standardDeviation, normalized)) +public class NormalDistribution(public val sampler: GaussianSampler) : Distribution1D { override fun probability(arg: Double): Double { val x1 = (arg - sampler.mean) / sampler.standardDeviation @@ -43,3 +38,9 @@ public class NormalDistribution(public val sampler: GaussianSampler) : Univariat private val SQRT2 = sqrt(2.0) } } + +public fun NormalDistribution( + mean: Double, + standardDeviation: Double, + normalized: NormalizedGaussianSampler = ZigguratNormalizedGaussianSampler, +): NormalDistribution = NormalDistribution(GaussianSampler(mean, standardDeviation, normalized)) diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/Sampler.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/Sampler.kt index 0dd121f3b..c96ecdc8c 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/Sampler.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/Sampler.kt @@ -67,3 +67,12 @@ public fun Sampler.sampleBuffer(generator: RandomGenerator, size: Int): @JvmName("sampleIntBuffer") public fun Sampler.sampleBuffer(generator: RandomGenerator, size: Int): Chain> = sampleBuffer(generator, size, ::IntBuffer) + + +/** + * Samples a [Buffer] of values from this [Sampler]. + */ +public suspend fun Sampler.nextBuffer(generator: RandomGenerator, size: Int): Buffer = + sampleBuffer(generator, size).first() + +//TODO add `context(RandomGenerator) Sampler.nextBuffer \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/UniformDistribution.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/UniformDistribution.kt index 970a3aab5..20cc0e802 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/UniformDistribution.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/stat/UniformDistribution.kt @@ -8,9 +8,9 @@ package space.kscience.kmath.stat import space.kscience.kmath.chains.Chain import space.kscience.kmath.chains.SimpleChain import space.kscience.kmath.distributions.Distribution -import space.kscience.kmath.distributions.UnivariateDistribution +import space.kscience.kmath.distributions.Distribution1D -public class UniformDistribution(public val range: ClosedFloatingPointRange) : UnivariateDistribution { +public class UniformDistribution(public val range: ClosedFloatingPointRange) : Distribution1D { private val length: Double = range.endInclusive - range.start override fun probability(arg: Double): Double = if (arg in range) 1.0 / length else 0.0 -- 2.34.1 From a2c0bc8a1012b437b1e91edd09e00327467e01f0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 8 Apr 2022 19:41:41 +0300 Subject: [PATCH 06/14] Another histogram refactor --- .../kmath/histogram/UniformHistogram1D.kt | 24 +++++++++++++++++++ .../kmath/histogram/UniformHistogram1DTest.kt | 16 ++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index 44638c29b..e2811602c 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -81,6 +81,30 @@ public class UniformHistogram1DGroup( builder.block() return UniformHistogram1D(this, map) } + + /** + * Re-bin given histogram to be compatible if exiting bin fully falls inside existing bin, this bin value + * is increased by one. If not, all bins including values from this bin are increased by fraction + * (conserving the norming). + */ + @UnstableKMathAPI + public fun produceFrom(histogram: Histogram1D): UniformHistogram1D = + if ((histogram as? UniformHistogram1D)?.group == this) histogram + else { + val map = HashMap() + histogram.bins.forEach { bin -> + val range = bin.domain.range + val indexOfLeft = getIndex(range.start) + val indexOfRight = getIndex(range.endInclusive) + val numBins = indexOfRight - indexOfLeft + 1 + for (i in indexOfLeft..indexOfRight) { + map[indexOfLeft] = with(valueAlgebra) { + (map[indexOfLeft] ?: zero) + bin.binValue / numBins + } + } + } + UniformHistogram1D(this, map) + } } public fun Histogram.Companion.uniform1D( diff --git a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt index d23afdb8b..8d5cb8fb1 100644 --- a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt @@ -12,14 +12,15 @@ import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.stat.RandomGenerator import space.kscience.kmath.stat.nextBuffer +import kotlin.native.concurrent.ThreadLocal import kotlin.test.Test import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class, UnstableKMathAPI::class) internal class UniformHistogram1DTest { + @Test fun normal() = runTest { - val generator = RandomGenerator.default(123) val distribution = NormalDistribution(0.0, 1.0) with(Histogram.uniform1D(DoubleField, 0.1)) { val h1 = produce(distribution.nextBuffer(generator, 10000)) @@ -31,4 +32,17 @@ internal class UniformHistogram1DTest { assertEquals(60000, h3.bins.sumOf { it.binValue }.toInt()) } } + + @Test + fun rebin() = runTest { + val h1 = Histogram.uniform1D(DoubleField, 0.1).produce(generator.nextDoubleBuffer(10000)) + val h2 = Histogram.uniform1D(DoubleField,0.3).produceFrom(h1) + + assertEquals(10000, h2.bins.sumOf { it.binValue }.toInt()) + } + + @ThreadLocal + companion object{ + private val generator = RandomGenerator.default(123) + } } \ No newline at end of file -- 2.34.1 From 3a2faa7da480cee97d2ddb682721ee85f4a57366 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 9 Apr 2022 10:18:18 +0300 Subject: [PATCH 07/14] Generalize UniformHistogram1D --- .../kmath/histogram/UniformHistogram1D.kt | 40 +++++++++++++------ .../kmath/histogram/UniformHistogram1DTest.kt | 14 +++++-- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index e2811602c..758e7969a 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -37,37 +37,53 @@ public class UniformHistogram1D( } } +/** + * An algebra for uniform histograms in 1D real space + */ public class UniformHistogram1DGroup( public val valueAlgebra: A, public val binSize: Double, public val startPoint: Double = 0.0, -) : Group>, ScaleOperations> where A : Ring, A : ScaleOperations { +) : Group>, ScaleOperations> where A : Ring, A : ScaleOperations { + override val zero: UniformHistogram1D by lazy { UniformHistogram1D(this, emptyMap()) } - public fun getIndex(at: Double): Int = floor((at - startPoint) / binSize).toInt() + /** + * Get index of a bin + */ + @PublishedApi + internal fun getIndex(at: Double): Int = floor((at - startPoint) / binSize).toInt() - override fun add(left: UniformHistogram1D, right: UniformHistogram1D): UniformHistogram1D = valueAlgebra { - require(left.group == this@UniformHistogram1DGroup) - require(right.group == this@UniformHistogram1DGroup) - val keys = left.values.keys + right.values.keys + override fun add( + left: Histogram1D, + right: Histogram1D, + ): UniformHistogram1D = valueAlgebra { + val leftUniform = produceFrom(left) + val rightUniform = produceFrom(right) + val keys = leftUniform.values.keys + rightUniform.values.keys UniformHistogram1D( this@UniformHistogram1DGroup, - keys.associateWith { (left.values[it] ?: valueAlgebra.zero) + (right.values[it] ?: valueAlgebra.zero) } + keys.associateWith { + (leftUniform.values[it] ?: valueAlgebra.zero) + (rightUniform.values[it] ?: valueAlgebra.zero) + } ) } - override fun UniformHistogram1D.unaryMinus(): UniformHistogram1D = valueAlgebra { - UniformHistogram1D(this@UniformHistogram1DGroup, values.mapValues { -it.value }) + override fun Histogram1D.unaryMinus(): UniformHistogram1D = valueAlgebra { + UniformHistogram1D(this@UniformHistogram1DGroup, produceFrom(this@unaryMinus).values.mapValues { -it.value }) } override fun scale( - a: UniformHistogram1D, + a: Histogram1D, value: Double, ): UniformHistogram1D = UniformHistogram1D( this@UniformHistogram1DGroup, - a.values.mapValues { valueAlgebra.scale(it.value, value) } + produceFrom(a).values.mapValues { valueAlgebra.scale(it.value, value) } ) + /** + * + */ public inline fun produce(block: Histogram1DBuilder.() -> Unit): UniformHistogram1D { val map = HashMap() val builder = object : Histogram1DBuilder { @@ -87,7 +103,7 @@ public class UniformHistogram1DGroup( * is increased by one. If not, all bins including values from this bin are increased by fraction * (conserving the norming). */ - @UnstableKMathAPI + @OptIn(UnstableKMathAPI::class) public fun produceFrom(histogram: Histogram1D): UniformHistogram1D = if ((histogram as? UniformHistogram1D)?.group == this) histogram else { diff --git a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt index 8d5cb8fb1..09bf3939d 100644 --- a/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/space/kscience/kmath/histogram/UniformHistogram1DTest.kt @@ -34,9 +34,17 @@ internal class UniformHistogram1DTest { } @Test - fun rebin() = runTest { - val h1 = Histogram.uniform1D(DoubleField, 0.1).produce(generator.nextDoubleBuffer(10000)) - val h2 = Histogram.uniform1D(DoubleField,0.3).produceFrom(h1) + fun rebinDown() = runTest { + val h1 = Histogram.uniform1D(DoubleField, 0.01).produce(generator.nextDoubleBuffer(10000)) + val h2 = Histogram.uniform1D(DoubleField,0.03).produceFrom(h1) + + assertEquals(10000, h2.bins.sumOf { it.binValue }.toInt()) + } + + @Test + fun rebinUp() = runTest { + val h1 = Histogram.uniform1D(DoubleField, 0.03).produce(generator.nextDoubleBuffer(10000)) + val h2 = Histogram.uniform1D(DoubleField,0.01).produceFrom(h1) assertEquals(10000, h2.bins.sumOf { it.binValue }.toInt()) } -- 2.34.1 From eba3a2526e0ca604947e71b5125565f57497623a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 09:48:55 +0300 Subject: [PATCH 08/14] [final] Generalize UniformHistogram1D --- .../kscience/kmath/histogram/Histogram1D.kt | 9 ++-- .../kmath/histogram/UniformHistogram1D.kt | 54 +++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) 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 237bd87b9..0c9352e76 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 @@ -6,6 +6,7 @@ package space.kscience.kmath.histogram import space.kscience.kmath.domains.Domain1D +import space.kscience.kmath.domains.center import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.asSequence @@ -16,7 +17,6 @@ import space.kscience.kmath.structures.Buffer * A univariate bin based on a range * * @property binValue The value of histogram including weighting - * @property standardDeviation Standard deviation of the bin value. Zero or negative if not applicable */ @UnstableKMathAPI public data class Bin1D, out V>( @@ -56,5 +56,8 @@ public fun Histogram1DBuilder.fill(array: DoubleArray): Unit = array.forEach(this::putValue) @UnstableKMathAPI -public fun Histogram1DBuilder.fill(buffer: Buffer): Unit = - buffer.asSequence().forEach(this::putValue) \ No newline at end of file +public fun Histogram1DBuilder.fill(buffer: Buffer): Unit = + buffer.asSequence().forEach(this::putValue) + +@OptIn(UnstableKMathAPI::class) +public val Bin1D.center: Double get() = domain.center \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index 758e7969a..93eca192f 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -17,7 +17,7 @@ import kotlin.math.floor @OptIn(UnstableKMathAPI::class) public class UniformHistogram1D( public val group: UniformHistogram1DGroup, - public val values: Map, + internal val values: Map, ) : Histogram1D { private val startPoint get() = group.startPoint @@ -82,7 +82,7 @@ public class UniformHistogram1DGroup( ) /** - * + * Fill histogram. */ public inline fun produce(block: Histogram1DBuilder.() -> Unit): UniformHistogram1D { val map = HashMap() @@ -104,23 +104,25 @@ public class UniformHistogram1DGroup( * (conserving the norming). */ @OptIn(UnstableKMathAPI::class) - public fun produceFrom(histogram: Histogram1D): UniformHistogram1D = - if ((histogram as? UniformHistogram1D)?.group == this) histogram - else { - val map = HashMap() - histogram.bins.forEach { bin -> - val range = bin.domain.range - val indexOfLeft = getIndex(range.start) - val indexOfRight = getIndex(range.endInclusive) - val numBins = indexOfRight - indexOfLeft + 1 - for (i in indexOfLeft..indexOfRight) { - map[indexOfLeft] = with(valueAlgebra) { - (map[indexOfLeft] ?: zero) + bin.binValue / numBins - } + public fun produceFrom( + histogram: Histogram1D, + ): UniformHistogram1D = if ((histogram as? UniformHistogram1D)?.group == this) { + histogram + } else { + val map = HashMap() + histogram.bins.forEach { bin -> + val range = bin.domain.range + val indexOfLeft = getIndex(range.start) + val indexOfRight = getIndex(range.endInclusive) + val numBins = indexOfRight - indexOfLeft + 1 + for (i in indexOfLeft..indexOfRight) { + map[indexOfLeft] = with(valueAlgebra) { + (map[indexOfLeft] ?: zero) + bin.binValue / numBins } } - UniformHistogram1D(this, map) } + UniformHistogram1D(this, map) + } } public fun Histogram.Companion.uniform1D( @@ -133,4 +135,22 @@ public fun Histogram.Companion.uniform1D( @UnstableKMathAPI public fun UniformHistogram1DGroup.produce( buffer: Buffer, -): UniformHistogram1D = produce { fill(buffer) } \ No newline at end of file +): UniformHistogram1D = produce { fill(buffer) } + +/** + * Map of bin centers to bin values + */ +@OptIn(UnstableKMathAPI::class) +public val UniformHistogram1D.binValues: Map + get() = bins.associate { it.center to it.binValue } + + +//TODO add normalized values inside Field-based histogram spaces with context receivers +///** +// * Map of bin centers to normalized bin values (bin size as normalization) +// */ +//@OptIn(UnstableKMathAPI::class) +//public val UniformHistogram1D.binValuesNormalized: Map +// get() = group.valueAlgebra { +// bins.associate { it.center to it.binValue / group.binSize } +// } \ No newline at end of file -- 2.34.1 From 86d89f89f904c8dd7bd70f5f596c78d63560a82f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 10:29:44 +0300 Subject: [PATCH 09/14] Update kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt Co-authored-by: Iaroslav Postovalov <38042667+CommanderTvis@users.noreply.github.com> --- .../space/kscience/kmath/histogram/IndexedHistogramGroup.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt index 9749f8403..b1d317a15 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt @@ -68,8 +68,9 @@ public interface IndexedHistogramGroup, V : Any> : Group.() -> Unit): IndexedHistogram override fun add(left: IndexedHistogram, right: IndexedHistogram): IndexedHistogram { - require(left.histogramSpace == this) { "Can't operate on a histogram produced by external space" } - require(right.histogramSpace == this) { "Can't operate on a histogram produced by external space" } + require(left.histogramSpace == this && right.histogramSpace == this) { + "A histogram belonging to a different group cannot be operated." + } return IndexedHistogram(this, histogramValueAlgebra { left.values + right.values }) } -- 2.34.1 From 40b088149bece382affa6016954e0f28fb0e35b5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 10:29:59 +0300 Subject: [PATCH 10/14] Update kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt Co-authored-by: Iaroslav Postovalov <38042667+CommanderTvis@users.noreply.github.com> --- .../space/kscience/kmath/histogram/IndexedHistogramGroup.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt index b1d317a15..22306049c 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt @@ -75,7 +75,7 @@ public interface IndexedHistogramGroup, V : Any> : Group, value: Double): IndexedHistogram { - require(a.histogramSpace == this) { "Can't operate on a histogram produced by external space" } + require(a.histogramSpace == this) { "A histogram belonging to a different group cannot be operated." } return IndexedHistogram(this, histogramValueAlgebra { a.values * value }) } -- 2.34.1 From 229c36b6616f1a24b85e327673c6283f3d2f9279 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 10:32:36 +0300 Subject: [PATCH 11/14] Accept changes --- .../kmath/histogram/IndexedHistogramGroup.kt | 16 ++++++++-------- .../kmath/histogram/UniformHistogram1D.kt | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt index 22306049c..6c04f01bf 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt @@ -28,20 +28,20 @@ public data class DomainBin, out V>( * @param V the type of bin value */ public class IndexedHistogram, V : Any>( - public val histogramSpace: IndexedHistogramGroup, + public val histogramGroup: IndexedHistogramGroup, public val values: StructureND, ) : Histogram> { override fun get(point: Point): DomainBin? { - val index = histogramSpace.getIndex(point) ?: return null - return histogramSpace.produceBin(index, values[index]) + val index = histogramGroup.getIndex(point) ?: return null + return histogramGroup.produceBin(index, values[index]) } - override val dimension: Int get() = histogramSpace.shape.size + override val dimension: Int get() = histogramGroup.shape.size override val bins: Iterable> - get() = DefaultStrides(histogramSpace.shape).asSequence().map { - histogramSpace.produceBin(it, values[it]) + get() = DefaultStrides(histogramGroup.shape).asSequence().map { + histogramGroup.produceBin(it, values[it]) }.asIterable() } @@ -68,14 +68,14 @@ public interface IndexedHistogramGroup, V : Any> : Group.() -> Unit): IndexedHistogram override fun add(left: IndexedHistogram, right: IndexedHistogram): IndexedHistogram { - require(left.histogramSpace == this && right.histogramSpace == this) { + require(left.histogramGroup == this && right.histogramGroup == this) { "A histogram belonging to a different group cannot be operated." } return IndexedHistogram(this, histogramValueAlgebra { left.values + right.values }) } override fun scale(a: IndexedHistogram, value: Double): IndexedHistogram { - require(a.histogramSpace == this) { "A histogram belonging to a different group cannot be operated." } + require(a.histogramGroup == this) { "A histogram belonging to a different group cannot be operated." } return IndexedHistogram(this, histogramValueAlgebra { a.values * value }) } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index 93eca192f..cb6572af9 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -46,7 +46,7 @@ public class UniformHistogram1DGroup( public val startPoint: Double = 0.0, ) : Group>, ScaleOperations> where A : Ring, A : ScaleOperations { - override val zero: UniformHistogram1D by lazy { UniformHistogram1D(this, emptyMap()) } + override val zero: UniformHistogram1D = UniformHistogram1D(this, emptyMap()) /** * Get index of a bin -- 2.34.1 From 27a252b63700782b5005ac987686641a9f151031 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 11:31:52 +0300 Subject: [PATCH 12/14] Accept changes, Update documentation --- .../space/kscience/kmath/domains/HyperSquareDomain.kt | 11 ++++++----- .../kscience/kmath/histogram/DoubleHistogramGroup.kt | 4 ++-- .../kscience/kmath/histogram/IndexedHistogramGroup.kt | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt index 2fac442e7..485416a69 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/domains/HyperSquareDomain.kt @@ -11,16 +11,17 @@ import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.indices /** - * - * HyperSquareDomain class. - * - * @author Alexander Nozik + * A hyper-square (or hyper-cube) real-space domain. It is formed by a [Buffer] of [lower] boundaries + * and a [Buffer] of upper boundaries. Upper should be greater or equals than lower. */ @UnstableKMathAPI public class HyperSquareDomain(public val lower: Buffer, public val upper: Buffer) : DoubleDomain { init { require(lower.size == upper.size) { - "Domain borders size mismatch. Lower borders size is ${lower.size}, but upper borders size is ${upper.size}" + "Domain borders size mismatch. Lower borders size is ${lower.size}, but upper borders size is ${upper.size}." + } + require(lower.indices.all { lower[it] <= upper[it] }) { + "Domain borders order mismatch. Lower borders must be less or equals than upper borders." } } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt index a80ed119c..bb66b5dc5 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt @@ -45,7 +45,7 @@ public class DoubleHistogramGroup( else -> floor((value - lower[axis]) / binSize[axis]).toInt() } - override fun getIndex(point: Buffer): IntArray = IntArray(dimension) { + override fun getIndexOrNull(point: Buffer): IntArray = IntArray(dimension) { getIndex(it, point[it]) } @@ -82,7 +82,7 @@ public class DoubleHistogramGroup( override val defaultValue: Double get() = 1.0 override fun putValue(point: Point, value: Double) { - val index = getIndex(point) + val index = getIndexOrNull(point) ndCounter[index].add(value) } } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt index 6c04f01bf..70913ecfb 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt @@ -33,7 +33,7 @@ public class IndexedHistogram, V : Any>( ) : Histogram> { override fun get(point: Point): DomainBin? { - val index = histogramGroup.getIndex(point) ?: return null + val index = histogramGroup.getIndexOrNull(point) ?: return null return histogramGroup.produceBin(index, values[index]) } @@ -54,9 +54,9 @@ public interface IndexedHistogramGroup, V : Any> : Group //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), /** - * Resolve index of the bin including given [point] + * Resolve index of the bin including given [point]. Return null if point is outside histogram area */ - public fun getIndex(point: Point): IntArray? + public fun getIndexOrNull(point: Point): IntArray? /** * Get a bin domain represented by given index -- 2.34.1 From 6247e79884069853f2c523013744826147b06354 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 13:41:41 +0300 Subject: [PATCH 13/14] Refactor multivariate histograms --- CHANGELOG.md | 1 + gradle.properties | 2 + .../space/kscience/kmath/nd/AlgebraND.kt | 2 +- .../space/kscience/kmath/nd/BufferND.kt | 23 ++- .../space/kscience/kmath/histogram/Counter.kt | 3 +- .../kmath/histogram/DoubleHistogramGroup.kt | 140 --------------- .../kscience/kmath/histogram/Histogram.kt | 9 + .../kscience/kmath/histogram/HistogramND.kt | 76 ++++++++ .../kmath/histogram/IndexedHistogramGroup.kt | 84 --------- .../histogram/UniformHistogramGroupND.kt | 163 ++++++++++++++++++ .../histogram/MultivariateHistogramTest.kt | 9 +- .../kmath/histogram/TreeHistogramSpace.kt | 2 +- 12 files changed, 275 insertions(+), 239 deletions(-) delete mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt create mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt delete mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt create mode 100644 kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index d710c330b..bc5447449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - Default Buffer and ND algebras are now Ops and lack neutral elements (0, 1) as well as algebra-level shapes. - Tensor algebra takes read-only structures as input and inherits AlgebraND - `UnivariateDistribution` renamed to `Distribution1D` +- Rework of histograms. ### Deprecated - Specialized `DoubleBufferAlgebra` diff --git a/gradle.properties b/gradle.properties index a7cd2f876..847c3dda6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,8 @@ kotlin.code.style=official kotlin.jupyter.add.scanner=false kotlin.mpp.stability.nowarn=true kotlin.native.ignoreDisabledTargets=true +kotlin.incremental.js.ir=true + org.gradle.configureondemand=true org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G org.gradle.parallel=true diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt index a071c1eb3..a9712e870 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt @@ -194,7 +194,7 @@ public interface RingOpsND> : RingOps>, Gro override fun multiply(left: StructureND, right: StructureND): StructureND = zip(left, right) { aValue, bValue -> multiply(aValue, bValue) } - //TODO move to extensions after KEEP-176 + //TODO move to extensions with context receivers /** * Multiplies an ND structure by an element of it. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt index 539499794..2401f6319 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt @@ -32,18 +32,23 @@ public open class BufferND( /** * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferND] */ -public inline fun StructureND.mapToBuffer( - factory: BufferFactory = Buffer.Companion::auto, +public inline fun StructureND.mapToBuffer( + factory: BufferFactory, crossinline transform: (T) -> R, -): BufferND { - return if (this is BufferND) - BufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) }) - else { - val strides = DefaultStrides(shape) - BufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) - } +): BufferND = if (this is BufferND) + BufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) }) +else { + val strides = DefaultStrides(shape) + BufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) } +/** + * Transform structure to a new structure using inferred [BufferFactory] + */ +public inline fun StructureND.mapToBuffer( + crossinline transform: (T) -> R, +): BufferND = mapToBuffer(Buffer.Companion::auto, transform) + /** * Represents [MutableStructureND] over [MutableBuffer]. * diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Counter.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Counter.kt index 291284444..fe3278026 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Counter.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/Counter.kt @@ -18,7 +18,8 @@ public interface Counter { public val value: T public companion object { - public fun double(): ObjectCounter = ObjectCounter(DoubleField) + public fun ofDouble(): ObjectCounter = ObjectCounter(DoubleField) + public fun of(group: Group): ObjectCounter = ObjectCounter(group) } } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt deleted file mode 100644 index bb66b5dc5..000000000 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/DoubleHistogramGroup.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.histogram - -import space.kscience.kmath.domains.HyperSquareDomain -import space.kscience.kmath.linear.Point -import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.nd.* -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.structures.* -import kotlin.math.floor - -/** - * Multivariate histogram space for hyper-square real-field bins. - */ -public class DoubleHistogramGroup( - private val lower: Buffer, - private val upper: Buffer, - private val binNums: IntArray = IntArray(lower.size) { 20 }, -) : IndexedHistogramGroup { - - init { - // argument checks - require(lower.size == upper.size) { "Dimension mismatch in histogram lower and upper limits." } - require(lower.size == binNums.size) { "Dimension mismatch in bin count." } - require(!lower.indices.any { upper[it] - lower[it] < 0 }) { "Range for one of axis is not strictly positive" } - } - - public val dimension: Int get() = lower.size - - override val shape: IntArray = IntArray(binNums.size) { binNums[it] + 2 } - override val histogramValueAlgebra: DoubleFieldND = DoubleField.ndAlgebra(*shape) - - private val binSize = DoubleBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] } - - /** - * Get internal [StructureND] bin index for given axis - */ - private fun getIndex(axis: Int, value: Double): Int = when { - value >= upper[axis] -> binNums[axis] + 1 // overflow - value < lower[axis] -> 0 // underflow - else -> floor((value - lower[axis]) / binSize[axis]).toInt() - } - - override fun getIndexOrNull(point: Buffer): IntArray = IntArray(dimension) { - getIndex(it, point[it]) - } - - @OptIn(UnstableKMathAPI::class) - override fun getDomain(index: IntArray): HyperSquareDomain { - val lowerBoundary = index.mapIndexed { axis, i -> - when (i) { - 0 -> Double.NEGATIVE_INFINITY - shape[axis] - 1 -> upper[axis] - else -> lower[axis] + (i.toDouble()) * binSize[axis] - } - }.asBuffer() - - val upperBoundary = index.mapIndexed { axis, i -> - when (i) { - 0 -> lower[axis] - shape[axis] - 1 -> Double.POSITIVE_INFINITY - else -> lower[axis] + (i.toDouble() + 1) * binSize[axis] - } - }.asBuffer() - - return HyperSquareDomain(lowerBoundary, upperBoundary) - } - - @OptIn(UnstableKMathAPI::class) - override fun produceBin(index: IntArray, value: Double): DomainBin { - val domain = getDomain(index) - return DomainBin(domain, value) - } - - override fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram { - val ndCounter = StructureND.auto(shape) { Counter.double() } - val hBuilder = object : HistogramBuilder { - override val defaultValue: Double get() = 1.0 - - override fun putValue(point: Point, value: Double) { - val index = getIndexOrNull(point) - ndCounter[index].add(value) - } - } - hBuilder.apply(builder) - val values: BufferND = ndCounter.mapToBuffer { it.value } - return IndexedHistogram(this, values) - } - - override fun IndexedHistogram.unaryMinus(): IndexedHistogram = this * (-1) - - public companion object { - /** - * Use it like - * ``` - *FastHistogram.fromRanges( - * (-1.0..1.0), - * (-1.0..1.0) - *) - *``` - */ - public fun fromRanges( - vararg ranges: ClosedFloatingPointRange, - ): DoubleHistogramGroup = DoubleHistogramGroup( - ranges.map(ClosedFloatingPointRange::start).asBuffer(), - ranges.map(ClosedFloatingPointRange::endInclusive).asBuffer() - ) - - /** - * Use it like - * ``` - *FastHistogram.fromRanges( - * (-1.0..1.0) to 50, - * (-1.0..1.0) to 32 - *) - *``` - */ - public fun fromRanges( - vararg ranges: Pair, Int>, - ): DoubleHistogramGroup = DoubleHistogramGroup( - ListBuffer( - ranges - .map(Pair, Int>::first) - .map(ClosedFloatingPointRange::start) - ), - - ListBuffer( - ranges - .map(Pair, Int>::first) - .map(ClosedFloatingPointRange::endInclusive) - ), - - ranges.map(Pair, Int>::second).toIntArray() - ) - } -} \ No newline at end of file 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 f9550df17..12edafd81 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 @@ -20,6 +20,15 @@ public interface Bin : Domain { public val binValue: V } +/** + * A simple histogram bin based on domain + */ +public data class DomainBin, D : Domain, out V>( + public val domain: D, + override val binValue: V, +) : Bin, Domain by domain + + public interface Histogram> { /** * Find existing bin, corresponding to given coordinates diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt new file mode 100644 index 000000000..2af03abd4 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.histogram + +import space.kscience.kmath.domains.Domain +import space.kscience.kmath.linear.Point +import space.kscience.kmath.nd.DefaultStrides +import space.kscience.kmath.nd.FieldOpsND +import space.kscience.kmath.nd.Shape +import space.kscience.kmath.nd.StructureND +import space.kscience.kmath.operations.Group +import space.kscience.kmath.operations.ScaleOperations +import space.kscience.kmath.operations.invoke + +/** + * @param T the type of the argument space + * @param V the type of bin value + */ +public class HistogramND, D : Domain, V : Any>( + public val group: HistogramGroupND, + internal val values: StructureND, +) : Histogram> { + + override fun get(point: Point): DomainBin? { + val index = group.getIndexOrNull(point) ?: return null + return group.produceBin(index, values[index]) + } + + override val dimension: Int get() = group.shape.size + + override val bins: Iterable> + get() = DefaultStrides(group.shape).asSequence().map { + group.produceBin(it, values[it]) + }.asIterable() +} + +/** + * A space for producing histograms with values in a NDStructure + */ +public interface HistogramGroupND, D : Domain, V : Any> : + Group>, ScaleOperations> { + public val shape: Shape + public val valueAlgebra: FieldOpsND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), + + /** + * Resolve index of the bin including given [point]. Return null if point is outside histogram area + */ + public fun getIndexOrNull(point: Point): IntArray? + + /** + * Get a bin domain represented by given index + */ + public fun getDomain(index: IntArray): Domain? + + public fun produceBin(index: IntArray, value: V): DomainBin + + public fun produce(builder: HistogramBuilder.() -> Unit): HistogramND + + override fun add(left: HistogramND, right: HistogramND): HistogramND { + require(left.group == this && right.group == this) { + "A histogram belonging to a different group cannot be operated." + } + return HistogramND(this, valueAlgebra { left.values + right.values }) + } + + override fun scale(a: HistogramND, value: Double): HistogramND { + require(a.group == this) { "A histogram belonging to a different group cannot be operated." } + return HistogramND(this, valueAlgebra { a.values * value }) + } + + override val zero: HistogramND get() = produce { } +} + diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt deleted file mode 100644 index 70913ecfb..000000000 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramGroup.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.histogram - -import space.kscience.kmath.domains.Domain -import space.kscience.kmath.linear.Point -import space.kscience.kmath.nd.DefaultStrides -import space.kscience.kmath.nd.FieldND -import space.kscience.kmath.nd.Shape -import space.kscience.kmath.nd.StructureND -import space.kscience.kmath.operations.Group -import space.kscience.kmath.operations.ScaleOperations -import space.kscience.kmath.operations.invoke - -/** - * A simple histogram bin based on domain - */ -public data class DomainBin, out V>( - public val domain: Domain, - override val binValue: V, -) : Bin, Domain by domain - -/** - * @param T the type of the argument space - * @param V the type of bin value - */ -public class IndexedHistogram, V : Any>( - public val histogramGroup: IndexedHistogramGroup, - public val values: StructureND, -) : Histogram> { - - override fun get(point: Point): DomainBin? { - val index = histogramGroup.getIndexOrNull(point) ?: return null - return histogramGroup.produceBin(index, values[index]) - } - - override val dimension: Int get() = histogramGroup.shape.size - - override val bins: Iterable> - get() = DefaultStrides(histogramGroup.shape).asSequence().map { - histogramGroup.produceBin(it, values[it]) - }.asIterable() -} - -/** - * A space for producing histograms with values in a NDStructure - */ -public interface IndexedHistogramGroup, V : Any> : Group>, - ScaleOperations> { - public val shape: Shape - public val histogramValueAlgebra: FieldND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), - - /** - * Resolve index of the bin including given [point]. Return null if point is outside histogram area - */ - public fun getIndexOrNull(point: Point): IntArray? - - /** - * Get a bin domain represented by given index - */ - public fun getDomain(index: IntArray): Domain? - - public fun produceBin(index: IntArray, value: V): DomainBin - - public fun produce(builder: HistogramBuilder.() -> Unit): IndexedHistogram - - override fun add(left: IndexedHistogram, right: IndexedHistogram): IndexedHistogram { - require(left.histogramGroup == this && right.histogramGroup == this) { - "A histogram belonging to a different group cannot be operated." - } - return IndexedHistogram(this, histogramValueAlgebra { left.values + right.values }) - } - - override fun scale(a: IndexedHistogram, value: Double): IndexedHistogram { - require(a.histogramGroup == this) { "A histogram belonging to a different group cannot be operated." } - return IndexedHistogram(this, histogramValueAlgebra { a.values * value }) - } - - override val zero: IndexedHistogram get() = produce { } -} - diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt new file mode 100644 index 000000000..b93da10b2 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:OptIn(UnstableKMathAPI::class) + +package space.kscience.kmath.histogram + +import space.kscience.kmath.domains.HyperSquareDomain +import space.kscience.kmath.linear.Point +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.nd.* +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.Field +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.* +import kotlin.math.floor + +public typealias HyperSquareBin = DomainBin + +/** + * Multivariate histogram space for hyper-square real-field bins. + * @param bufferFactory is an optional parameter used to optimize buffer production. + */ +public class UniformHistogramGroupND>( + override val valueAlgebra: FieldOpsND, + private val lower: Buffer, + private val upper: Buffer, + private val binNums: IntArray = IntArray(lower.size) { 20 }, + private val bufferFactory: BufferFactory = Buffer.Companion::boxing, +) : HistogramGroupND { + + init { + // argument checks + require(lower.size == upper.size) { "Dimension mismatch in histogram lower and upper limits." } + require(lower.size == binNums.size) { "Dimension mismatch in bin count." } + require(!lower.indices.any { upper[it] - lower[it] < 0 }) { "Range for one of axis is not strictly positive" } + } + + public val dimension: Int get() = lower.size + + override val shape: IntArray = IntArray(binNums.size) { binNums[it] + 2 } + + private val binSize = DoubleBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] } + + /** + * Get internal [StructureND] bin index for given axis + */ + private fun getIndex(axis: Int, value: Double): Int = when { + value >= upper[axis] -> binNums[axis] + 1 // overflow + value < lower[axis] -> 0 // underflow + else -> floor((value - lower[axis]) / binSize[axis]).toInt() + } + + override fun getIndexOrNull(point: Buffer): IntArray = IntArray(dimension) { + getIndex(it, point[it]) + } + + override fun getDomain(index: IntArray): HyperSquareDomain { + val lowerBoundary = index.mapIndexed { axis, i -> + when (i) { + 0 -> Double.NEGATIVE_INFINITY + shape[axis] - 1 -> upper[axis] + else -> lower[axis] + (i.toDouble()) * binSize[axis] + } + }.asBuffer() + + val upperBoundary = index.mapIndexed { axis, i -> + when (i) { + 0 -> lower[axis] + shape[axis] - 1 -> Double.POSITIVE_INFINITY + else -> lower[axis] + (i.toDouble() + 1) * binSize[axis] + } + }.asBuffer() + + return HyperSquareDomain(lowerBoundary, upperBoundary) + } + + override fun produceBin(index: IntArray, value: V): HyperSquareBin { + val domain = getDomain(index) + return DomainBin(domain, value) + } + + + override fun produce(builder: HistogramBuilder.() -> Unit): HistogramND { + val ndCounter = StructureND.buffered(shape) { Counter.of(valueAlgebra.elementAlgebra) } + val hBuilder = object : HistogramBuilder { + override val defaultValue: V get() = valueAlgebra.elementAlgebra.one + + override fun putValue(point: Point, value: V) = with(valueAlgebra.elementAlgebra) { + val index = getIndexOrNull(point) + ndCounter[index].add(value) + } + } + hBuilder.apply(builder) + val values: BufferND = ndCounter.mapToBuffer(bufferFactory) { it.value } + return HistogramND(this, values) + } + + override fun HistogramND.unaryMinus(): HistogramND = + this * (-1) +} + +/** + * Use it like + * ``` + *FastHistogram.fromRanges( + * (-1.0..1.0), + * (-1.0..1.0) + *) + *``` + */ +public fun > Histogram.Companion.uniformNDFromRanges( + valueAlgebra: FieldOpsND, + vararg ranges: ClosedFloatingPointRange, + bufferFactory: BufferFactory = Buffer.Companion::boxing, +): UniformHistogramGroupND = UniformHistogramGroupND( + valueAlgebra, + ranges.map(ClosedFloatingPointRange::start).asBuffer(), + ranges.map(ClosedFloatingPointRange::endInclusive).asBuffer(), + bufferFactory = bufferFactory +) + +public fun Histogram.Companion.uniformDoubleNDFromRanges( + vararg ranges: ClosedFloatingPointRange, +): UniformHistogramGroupND = + uniformNDFromRanges(DoubleFieldOpsND, *ranges, bufferFactory = ::DoubleBuffer) + + +/** + * Use it like + * ``` + *FastHistogram.fromRanges( + * (-1.0..1.0) to 50, + * (-1.0..1.0) to 32 + *) + *``` + */ +public fun > Histogram.Companion.uniformNDFromRanges( + valueAlgebra: FieldOpsND, + vararg ranges: Pair, Int>, + bufferFactory: BufferFactory = Buffer.Companion::boxing, +): UniformHistogramGroupND = UniformHistogramGroupND( + valueAlgebra, + ListBuffer( + ranges + .map(Pair, Int>::first) + .map(ClosedFloatingPointRange::start) + ), + ListBuffer( + ranges + .map(Pair, Int>::first) + .map(ClosedFloatingPointRange::endInclusive) + ), + ranges.map(Pair, Int>::second).toIntArray(), + bufferFactory = bufferFactory +) + +public fun Histogram.Companion.uniformDoubleNDFromRanges( + vararg ranges: Pair, Int>, +): UniformHistogramGroupND = + uniformNDFromRanges(DoubleFieldOpsND, *ranges, bufferFactory = ::DoubleBuffer) \ No newline at end of file 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 1426b35fa..ca7c2f324 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 @@ -3,8 +3,11 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:OptIn(UnstableKMathAPI::class) + package space.kscience.kmath.histogram +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.DefaultStrides import space.kscience.kmath.operations.invoke import space.kscience.kmath.real.DoubleVector @@ -14,7 +17,7 @@ import kotlin.test.* internal class MultivariateHistogramTest { @Test fun testSinglePutHistogram() { - val hSpace = DoubleHistogramGroup.fromRanges( + val hSpace = Histogram.uniformDoubleNDFromRanges( (-1.0..1.0), (-1.0..1.0) ) @@ -29,7 +32,7 @@ internal class MultivariateHistogramTest { @Test fun testSequentialPut() { - val hSpace = DoubleHistogramGroup.fromRanges( + val hSpace = Histogram.uniformDoubleNDFromRanges( (-1.0..1.0), (-1.0..1.0), (-1.0..1.0) @@ -49,7 +52,7 @@ internal class MultivariateHistogramTest { @Test fun testHistogramAlgebra() { - DoubleHistogramGroup.fromRanges( + Histogram.uniformDoubleNDFromRanges( (-1.0..1.0), (-1.0..1.0), (-1.0..1.0) 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 e85bb0a3c..bff20c22c 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 @@ -45,7 +45,7 @@ internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) override val defaultValue: Double get() = 1.0 - internal class BinCounter(val domain: DoubleDomain1D, val counter: Counter = Counter.double()) : + internal class BinCounter(val domain: DoubleDomain1D, val counter: Counter = Counter.ofDouble()) : ClosedRange by domain.range private val bins: TreeMap = TreeMap() -- 2.34.1 From 1295a407c37db40c0fa993c7b7c45068e623c079 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Apr 2022 15:29:46 +0300 Subject: [PATCH 14/14] Refactor tree histogram --- gradle.properties | 2 +- .../space/kscience/kmath/structures/Buffer.kt | 10 + .../kscience/kmath/histogram/Histogram1D.kt | 1 + .../kscience/kmath/histogram/HistogramND.kt | 6 +- .../kmath/histogram/UniformHistogram1D.kt | 4 +- .../histogram/UniformHistogramGroupND.kt | 16 +- .../kmath/histogram/TreeHistogramGroup.kt | 179 ++++++++++++++++ .../kmath/histogram/TreeHistogramSpace.kt | 199 ------------------ .../kmath/histogram/TreeHistogramTest.kt | 11 +- 9 files changed, 212 insertions(+), 216 deletions(-) create mode 100644 kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramGroup.kt delete mode 100644 kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt diff --git a/gradle.properties b/gradle.properties index 847c3dda6..80108737e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.code.style=official kotlin.jupyter.add.scanner=false kotlin.mpp.stability.nowarn=true kotlin.native.ignoreDisabledTargets=true -kotlin.incremental.js.ir=true +#kotlin.incremental.js.ir=true org.gradle.configureondemand=true org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt index 58c6d5ded..a1b0307c4 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt @@ -105,6 +105,16 @@ public interface Buffer { */ public val Buffer<*>.indices: IntRange get() = 0 until size +public fun Buffer.first(): T { + require(size > 0) { "Can't get the first element of empty buffer" } + return get(0) +} + +public fun Buffer.last(): T { + require(size > 0) { "Can't get the last element of empty buffer" } + return get(size - 1) +} + /** * Immutable wrapper for [MutableBuffer]. * 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 0c9352e76..710e4478b 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 @@ -43,6 +43,7 @@ public interface Histogram1DBuilder : HistogramBuilder, value: V) { + require(point.size == 1) { "Only points with single value could be used in Histogram1D" } putValue(point[0], value) } } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt index 2af03abd4..68b24db5d 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/HistogramND.kt @@ -43,7 +43,7 @@ public class HistogramND, D : Domain, V : Any>( public interface HistogramGroupND, D : Domain, V : Any> : Group>, ScaleOperations> { public val shape: Shape - public val valueAlgebra: FieldOpsND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), + public val valueAlgebraND: FieldOpsND //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape), /** * Resolve index of the bin including given [point]. Return null if point is outside histogram area @@ -63,12 +63,12 @@ public interface HistogramGroupND, D : Domain, V : Any> : require(left.group == this && right.group == this) { "A histogram belonging to a different group cannot be operated." } - return HistogramND(this, valueAlgebra { left.values + right.values }) + return HistogramND(this, valueAlgebraND { left.values + right.values }) } override fun scale(a: HistogramND, value: Double): HistogramND { require(a.group == this) { "A histogram belonging to a different group cannot be operated." } - return HistogramND(this, valueAlgebra { a.values * value }) + return HistogramND(this, valueAlgebraND { a.values * value }) } override val zero: HistogramND get() = produce { } diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt index cb6572af9..e13928394 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogram1D.kt @@ -126,11 +126,11 @@ public class UniformHistogram1DGroup( } public fun Histogram.Companion.uniform1D( - algebra: A, + valueAlgebra: A, binSize: Double, startPoint: Double = 0.0, ): UniformHistogram1DGroup where A : Ring, A : ScaleOperations = - UniformHistogram1DGroup(algebra, binSize, startPoint) + UniformHistogram1DGroup(valueAlgebra, binSize, startPoint) @UnstableKMathAPI public fun UniformHistogram1DGroup.produce( diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt index b93da10b2..90ec29ce3 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/UniformHistogramGroupND.kt @@ -24,7 +24,7 @@ public typealias HyperSquareBin = DomainBin * @param bufferFactory is an optional parameter used to optimize buffer production. */ public class UniformHistogramGroupND>( - override val valueAlgebra: FieldOpsND, + override val valueAlgebraND: FieldOpsND, private val lower: Buffer, private val upper: Buffer, private val binNums: IntArray = IntArray(lower.size) { 20 }, @@ -84,11 +84,11 @@ public class UniformHistogramGroupND>( override fun produce(builder: HistogramBuilder.() -> Unit): HistogramND { - val ndCounter = StructureND.buffered(shape) { Counter.of(valueAlgebra.elementAlgebra) } + val ndCounter = StructureND.buffered(shape) { Counter.of(valueAlgebraND.elementAlgebra) } val hBuilder = object : HistogramBuilder { - override val defaultValue: V get() = valueAlgebra.elementAlgebra.one + override val defaultValue: V get() = valueAlgebraND.elementAlgebra.one - override fun putValue(point: Point, value: V) = with(valueAlgebra.elementAlgebra) { + override fun putValue(point: Point, value: V) = with(valueAlgebraND.elementAlgebra) { val index = getIndexOrNull(point) ndCounter[index].add(value) } @@ -112,11 +112,11 @@ public class UniformHistogramGroupND>( *``` */ public fun > Histogram.Companion.uniformNDFromRanges( - valueAlgebra: FieldOpsND, + valueAlgebraND: FieldOpsND, vararg ranges: ClosedFloatingPointRange, bufferFactory: BufferFactory = Buffer.Companion::boxing, ): UniformHistogramGroupND = UniformHistogramGroupND( - valueAlgebra, + valueAlgebraND, ranges.map(ClosedFloatingPointRange::start).asBuffer(), ranges.map(ClosedFloatingPointRange::endInclusive).asBuffer(), bufferFactory = bufferFactory @@ -138,11 +138,11 @@ public fun Histogram.Companion.uniformDoubleNDFromRanges( *``` */ public fun > Histogram.Companion.uniformNDFromRanges( - valueAlgebra: FieldOpsND, + valueAlgebraND: FieldOpsND, vararg ranges: Pair, Int>, bufferFactory: BufferFactory = Buffer.Companion::boxing, ): UniformHistogramGroupND = UniformHistogramGroupND( - valueAlgebra, + valueAlgebraND, ListBuffer( ranges .map(Pair, Int>::first) diff --git a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramGroup.kt b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramGroup.kt new file mode 100644 index 000000000..6bec01f9b --- /dev/null +++ b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramGroup.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:OptIn(UnstableKMathAPI::class) + +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.misc.sorted +import space.kscience.kmath.operations.Group +import space.kscience.kmath.operations.Ring +import space.kscience.kmath.operations.ScaleOperations +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.first +import space.kscience.kmath.structures.indices +import space.kscience.kmath.structures.last +import java.util.* + +private fun > TreeMap.getBin(value: Double): B? { + // check ceiling entry and return it if it is what needed + val ceil = ceilingEntry(value)?.value + if (ceil != null && value in ceil) return ceil + //check floor entry + val floor = floorEntry(value)?.value + if (floor != null && value in floor) return floor + //neither is valid, not found + return null +} + +//public data class ValueAndError(val value: Double, val error: Double) +// +//public typealias WeightedBin1D = Bin1D + +/** + * A histogram based on a tree map of values + */ +public class TreeHistogram( + private val binMap: TreeMap>, +) : Histogram1D { + override fun get(value: Double): Bin1D? = binMap.getBin(value) + override val bins: Collection> get() = binMap.values +} + +/** + * A space for univariate histograms with variable bin borders based on a tree map + */ +public class TreeHistogramGroup( + public val valueAlgebra: A, + @PublishedApi internal val binFactory: (Double) -> DoubleDomain1D, +) : Group>, ScaleOperations> where A : Ring, A : ScaleOperations { + + internal inner class DomainCounter(val domain: DoubleDomain1D, val counter: Counter = Counter.of(valueAlgebra)) : + ClosedRange by domain.range + + @PublishedApi + internal inner class TreeHistogramBuilder : Histogram1DBuilder { + + override val defaultValue: V get() = valueAlgebra.one + + private val bins: TreeMap = TreeMap() + + private fun createBin(value: Double): DomainCounter { + val binDefinition: DoubleDomain1D = binFactory(value) + val newBin = DomainCounter(binDefinition) + synchronized(this) { + bins[binDefinition.center] = newBin + } + return newBin + } + + /** + * Thread safe put operation + */ + override fun putValue(at: Double, value: V) { + (bins.getBin(at) ?: createBin(at)).counter.add(value) + } + + fun build(): TreeHistogram { + val map = bins.mapValuesTo(TreeMap>()) { (_, binCounter) -> + Bin1D(binCounter.domain, binCounter.counter.value) + } + return TreeHistogram(map) + } + } + + public inline fun produce(block: Histogram1DBuilder.() -> Unit): TreeHistogram = + TreeHistogramBuilder().apply(block).build() + + override fun add( + left: TreeHistogram, + right: TreeHistogram, + ): TreeHistogram { + val bins = TreeMap>().apply { + (left.bins.map { it.domain } union right.bins.map { it.domain }).forEach { def -> + put( + def.center, + Bin1D( + def, + with(valueAlgebra) { + (left[def.center]?.binValue ?: zero) + (right[def.center]?.binValue ?: zero) + } + ) + ) + } + } + return TreeHistogram(bins) + } + + override fun scale(a: TreeHistogram, value: Double): TreeHistogram { + val bins = TreeMap>().apply { + a.bins.forEach { bin -> + put( + bin.domain.center, + Bin1D(bin.domain, valueAlgebra.scale(bin.binValue, value)) + ) + } + } + + return TreeHistogram(bins) + } + + override fun TreeHistogram.unaryMinus(): TreeHistogram = this * (-1) + + override val zero: TreeHistogram = produce { } +} + + +///** +// * Build and fill a histogram with custom borders. Returns a read-only histogram. +// */ +//public inline fun Histogram.custom( +// borders: DoubleArray, +// builder: Histogram1DBuilder.() -> Unit, +//): TreeHistogram = custom(borders).fill(builder) +// +// +///** +// * Build and fill a [DoubleHistogram1D]. Returns a read-only histogram. +// */ +//public fun uniform( +// binSize: Double, +// start: Double = 0.0, +//): TreeHistogramSpace = TreeHistogramSpace { value -> +// val center = start + binSize * floor((value - start) / binSize + 0.5) +// DoubleDomain1D((center - binSize / 2)..(center + binSize / 2)) +//} + +/** + * Create a histogram group with custom cell borders + */ +public fun Histogram.Companion.custom1D( + valueAlgebra: A, + borders: Buffer, +): TreeHistogramGroup where A : Ring, A : ScaleOperations { + val sorted = borders.sorted() + + return TreeHistogramGroup(valueAlgebra) { value -> + when { + value <= sorted.first() -> DoubleDomain1D( + Double.NEGATIVE_INFINITY..sorted.first() + ) + + value > sorted.last() -> DoubleDomain1D( + sorted.last()..Double.POSITIVE_INFINITY + ) + + else -> { + val index = sorted.indices.first { value <= sorted[it] } + val left = sorted[index - 1] + val right = sorted[index] + DoubleDomain1D(left..right) + } + } + } +} \ 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 deleted file mode 100644 index bff20c22c..000000000 --- a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -@file:OptIn(UnstableKMathAPI::class) - -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 -import space.kscience.kmath.structures.Buffer -import java.util.* -import kotlin.math.abs -import kotlin.math.floor -import kotlin.math.sqrt - -private fun > TreeMap.getBin(value: Double): B? { - // check ceiling entry and return it if it is what needed - val ceil = ceilingEntry(value)?.value - if (ceil != null && value in ceil) return ceil - //check floor entry - val floor = floorEntry(value)?.value - if (floor != null && value in floor) return floor - //neither is valid, not found - return null -} - -public data class ValueAndError(val value: Double, val error: Double) - -public typealias WeightedBin1D = Bin1D - -public class TreeHistogram( - private val binMap: TreeMap, -) : Histogram1D { - override fun get(value: Double): WeightedBin1D? = binMap.getBin(value) - override val bins: Collection get() = binMap.values -} - -@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.ofDouble()) : - ClosedRange by domain.range - - private val bins: TreeMap = TreeMap() - - fun get(value: Double): BinCounter? = bins.getBin(value) - - fun createBin(value: Double): BinCounter { - val binDefinition: DoubleDomain1D = binFactory(value) - val newBin = BinCounter(binDefinition) - synchronized(this) { - bins[binDefinition.center] = newBin - } - return newBin - } - - /** - * Thread safe put operation - */ - override fun putValue(at: Double, value: Double) { - (get(at) ?: createBin(at)).apply { - counter.add(value) - } - } - - override fun putValue(point: Buffer, value: Double) { - require(point.size == 1) { "Only points with single value could be used in univariate histogram" } - putValue(point[0], value.toDouble()) - } - - fun build(): TreeHistogram { - val map = bins.mapValuesTo(TreeMap()) { (_, binCounter) -> - val count: Double = binCounter.counter.value - WeightedBin1D(binCounter.domain, ValueAndError(count, sqrt(count))) - } - return TreeHistogram(map) - } -} - -/** - * A space for univariate histograms with variable bin borders based on a tree map - */ -public class TreeHistogramSpace( - @PublishedApi internal val binFactory: (Double) -> DoubleDomain1D, -) : Group, ScaleOperations { - - public inline fun fill(block: Histogram1DBuilder.() -> Unit): TreeHistogram = - TreeHistogramBuilder(binFactory).apply(block).build() - - override fun add( - 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 { - (left.bins.map { it.domain } union right.bins.map { it.domain }).forEach { def -> - put( - def.center, - WeightedBin1D( - def, - 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) - ) - ) - ) - } - } - return TreeHistogram(bins) - } - - override fun scale(a: TreeHistogram, value: Double): TreeHistogram { - val bins = TreeMap().apply { - a.bins.forEach { bin -> - put( - bin.domain.center, - WeightedBin1D( - bin.domain, - ValueAndError( - bin.binValue.value * value, - abs(bin.binValue.error * value) - ) - ) - ) - } - } - - return TreeHistogram(bins) - } - - override fun TreeHistogram.unaryMinus(): TreeHistogram = this * (-1) - - override val zero: TreeHistogram by lazy { fill { } } - - public companion object { - /** - * Build and fill a [TreeHistogram]. Returns a read-only histogram. - */ - public inline fun uniform( - binSize: Double, - start: Double = 0.0, - 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, - ): TreeHistogram = custom(borders).fill(builder) - - - /** - * Build and fill a [DoubleHistogram1D]. Returns a read-only histogram. - */ - public fun uniform( - binSize: Double, - start: Double = 0.0, - ): TreeHistogramSpace = TreeHistogramSpace { value -> - val center = start + binSize * floor((value - start) / binSize + 0.5) - DoubleDomain1D((center - binSize / 2)..(center + binSize / 2)) - } - - /** - * Create a histogram with custom cell borders - */ - public fun custom(borders: DoubleArray): TreeHistogramSpace { - val sorted = borders.sortedArray() - - return TreeHistogramSpace { value -> - when { - value < sorted.first() -> DoubleDomain1D( - Double.NEGATIVE_INFINITY..sorted.first() - ) - - value > sorted.last() -> DoubleDomain1D( - sorted.last()..Double.POSITIVE_INFINITY - ) - - else -> { - val index = sorted.indices.first { value > sorted[it] } - val left = sorted[index] - val right = sorted[index + 1] - DoubleDomain1D(left..right) - } - } - } - } - } -} \ No newline at end of file diff --git a/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt b/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt index f1a8f953b..c4eeb53cc 100644 --- a/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt +++ b/kmath-histograms/src/jvmTest/kotlin/space/kscience/kmath/histogram/TreeHistogramTest.kt @@ -6,19 +6,24 @@ package space.kscience.kmath.histogram import org.junit.jupiter.api.Test +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.real.step import kotlin.random.Random +import kotlin.test.assertEquals import kotlin.test.assertTrue class TreeHistogramTest { @Test fun normalFill() { - val histogram = TreeHistogramSpace.uniform(0.1) { + val random = Random(123) + val histogram = Histogram.custom1D(DoubleField, 0.0..1.0 step 0.1).produce { repeat(100_000) { - putValue(Random.nextDouble()) + putValue(random.nextDouble()) } } - assertTrue { histogram.bins.count() > 10 } + assertTrue { histogram.bins.count() > 8} + assertEquals(100_000, histogram.bins.sumOf { it.binValue }.toInt()) } } \ No newline at end of file -- 2.34.1