Complete refactor of histograms API #476
@ -6,7 +6,7 @@ kotlin.code.style=official
|
|||||||
kotlin.jupyter.add.scanner=false
|
kotlin.jupyter.add.scanner=false
|
||||||
kotlin.mpp.stability.nowarn=true
|
kotlin.mpp.stability.nowarn=true
|
||||||
kotlin.native.ignoreDisabledTargets=true
|
kotlin.native.ignoreDisabledTargets=true
|
||||||
kotlin.incremental.js.ir=true
|
#kotlin.incremental.js.ir=true
|
||||||
|
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G
|
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G
|
||||||
|
@ -105,6 +105,16 @@ public interface Buffer<out T> {
|
|||||||
*/
|
*/
|
||||||
public val Buffer<*>.indices: IntRange get() = 0 until size
|
public val Buffer<*>.indices: IntRange get() = 0 until size
|
||||||
|
|
||||||
|
public fun <T> Buffer<T>.first(): T {
|
||||||
|
require(size > 0) { "Can't get the first element of empty buffer" }
|
||||||
|
return get(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T> Buffer<T>.last(): T {
|
||||||
|
require(size > 0) { "Can't get the last element of empty buffer" }
|
||||||
|
return get(size - 1)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable wrapper for [MutableBuffer].
|
* Immutable wrapper for [MutableBuffer].
|
||||||
*
|
*
|
||||||
|
@ -43,6 +43,7 @@ public interface Histogram1DBuilder<in T : Any, V : Any> : HistogramBuilder<T, V
|
|||||||
public fun putValue(at: T, value: V = defaultValue)
|
public fun putValue(at: T, value: V = defaultValue)
|
||||||
|
|
||||||
override fun putValue(point: Point<out T>, value: V) {
|
override fun putValue(point: Point<out T>, value: V) {
|
||||||
|
require(point.size == 1) { "Only points with single value could be used in Histogram1D" }
|
||||||
putValue(point[0], value)
|
putValue(point[0], value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public class HistogramND<T : Comparable<T>, D : Domain<T>, V : Any>(
|
|||||||
public interface HistogramGroupND<T : Comparable<T>, D : Domain<T>, V : Any> :
|
public interface HistogramGroupND<T : Comparable<T>, D : Domain<T>, V : Any> :
|
||||||
Group<HistogramND<T, D, V>>, ScaleOperations<HistogramND<T, D, V>> {
|
Group<HistogramND<T, D, V>>, ScaleOperations<HistogramND<T, D, V>> {
|
||||||
public val shape: Shape
|
public val shape: Shape
|
||||||
public val valueAlgebra: FieldOpsND<V, *> //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape),
|
public val valueAlgebraND: FieldOpsND<V, *> //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve index of the bin including given [point]. Return null if point is outside histogram area
|
* Resolve index of the bin including given [point]. Return null if point is outside histogram area
|
||||||
@ -63,12 +63,12 @@ public interface HistogramGroupND<T : Comparable<T>, D : Domain<T>, V : Any> :
|
|||||||
require(left.group == this && right.group == this) {
|
require(left.group == this && right.group == this) {
|
||||||
"A histogram belonging to a different group cannot be operated."
|
"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<T, D, V>, value: Double): HistogramND<T, D, V> {
|
override fun scale(a: HistogramND<T, D, V>, value: Double): HistogramND<T, D, V> {
|
||||||
require(a.group == this) { "A histogram belonging to a different group cannot be operated." }
|
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<T, D, V> get() = produce { }
|
override val zero: HistogramND<T, D, V> get() = produce { }
|
||||||
|
@ -126,11 +126,11 @@ public class UniformHistogram1DGroup<V : Any, A>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun <V : Any, A> Histogram.Companion.uniform1D(
|
public fun <V : Any, A> Histogram.Companion.uniform1D(
|
||||||
algebra: A,
|
valueAlgebra: A,
|
||||||
binSize: Double,
|
binSize: Double,
|
||||||
startPoint: Double = 0.0,
|
startPoint: Double = 0.0,
|
||||||
): UniformHistogram1DGroup<V, A> where A : Ring<V>, A : ScaleOperations<V> =
|
): UniformHistogram1DGroup<V, A> where A : Ring<V>, A : ScaleOperations<V> =
|
||||||
UniformHistogram1DGroup(algebra, binSize, startPoint)
|
UniformHistogram1DGroup(valueAlgebra, binSize, startPoint)
|
||||||
|
|
||||||
@UnstableKMathAPI
|
@UnstableKMathAPI
|
||||||
public fun <V : Any> UniformHistogram1DGroup<V, *>.produce(
|
public fun <V : Any> UniformHistogram1DGroup<V, *>.produce(
|
||||||
|
@ -24,7 +24,7 @@ public typealias HyperSquareBin<V> = DomainBin<Double, HyperSquareDomain, V>
|
|||||||
* @param bufferFactory is an optional parameter used to optimize buffer production.
|
* @param bufferFactory is an optional parameter used to optimize buffer production.
|
||||||
*/
|
*/
|
||||||
public class UniformHistogramGroupND<V : Any, A : Field<V>>(
|
public class UniformHistogramGroupND<V : Any, A : Field<V>>(
|
||||||
override val valueAlgebra: FieldOpsND<V, A>,
|
override val valueAlgebraND: FieldOpsND<V, A>,
|
||||||
private val lower: Buffer<Double>,
|
private val lower: Buffer<Double>,
|
||||||
private val upper: Buffer<Double>,
|
private val upper: Buffer<Double>,
|
||||||
private val binNums: IntArray = IntArray(lower.size) { 20 },
|
private val binNums: IntArray = IntArray(lower.size) { 20 },
|
||||||
@ -84,11 +84,11 @@ public class UniformHistogramGroupND<V : Any, A : Field<V>>(
|
|||||||
|
|
||||||
|
|
||||||
override fun produce(builder: HistogramBuilder<Double, V>.() -> Unit): HistogramND<Double, HyperSquareDomain, V> {
|
override fun produce(builder: HistogramBuilder<Double, V>.() -> Unit): HistogramND<Double, HyperSquareDomain, V> {
|
||||||
val ndCounter = StructureND.buffered(shape) { Counter.of(valueAlgebra.elementAlgebra) }
|
val ndCounter = StructureND.buffered(shape) { Counter.of(valueAlgebraND.elementAlgebra) }
|
||||||
val hBuilder = object : HistogramBuilder<Double, V> {
|
val hBuilder = object : HistogramBuilder<Double, V> {
|
||||||
override val defaultValue: V get() = valueAlgebra.elementAlgebra.one
|
override val defaultValue: V get() = valueAlgebraND.elementAlgebra.one
|
||||||
|
|
||||||
override fun putValue(point: Point<out Double>, value: V) = with(valueAlgebra.elementAlgebra) {
|
override fun putValue(point: Point<out Double>, value: V) = with(valueAlgebraND.elementAlgebra) {
|
||||||
val index = getIndexOrNull(point)
|
val index = getIndexOrNull(point)
|
||||||
ndCounter[index].add(value)
|
ndCounter[index].add(value)
|
||||||
}
|
}
|
||||||
@ -112,11 +112,11 @@ public class UniformHistogramGroupND<V : Any, A : Field<V>>(
|
|||||||
*```
|
*```
|
||||||
*/
|
*/
|
||||||
public fun <V : Any, A : Field<V>> Histogram.Companion.uniformNDFromRanges(
|
public fun <V : Any, A : Field<V>> Histogram.Companion.uniformNDFromRanges(
|
||||||
valueAlgebra: FieldOpsND<V, A>,
|
valueAlgebraND: FieldOpsND<V, A>,
|
||||||
vararg ranges: ClosedFloatingPointRange<Double>,
|
vararg ranges: ClosedFloatingPointRange<Double>,
|
||||||
bufferFactory: BufferFactory<V> = Buffer.Companion::boxing,
|
bufferFactory: BufferFactory<V> = Buffer.Companion::boxing,
|
||||||
): UniformHistogramGroupND<V, A> = UniformHistogramGroupND(
|
): UniformHistogramGroupND<V, A> = UniformHistogramGroupND(
|
||||||
valueAlgebra,
|
valueAlgebraND,
|
||||||
ranges.map(ClosedFloatingPointRange<Double>::start).asBuffer(),
|
ranges.map(ClosedFloatingPointRange<Double>::start).asBuffer(),
|
||||||
ranges.map(ClosedFloatingPointRange<Double>::endInclusive).asBuffer(),
|
ranges.map(ClosedFloatingPointRange<Double>::endInclusive).asBuffer(),
|
||||||
bufferFactory = bufferFactory
|
bufferFactory = bufferFactory
|
||||||
@ -138,11 +138,11 @@ public fun Histogram.Companion.uniformDoubleNDFromRanges(
|
|||||||
*```
|
*```
|
||||||
*/
|
*/
|
||||||
public fun <V : Any, A : Field<V>> Histogram.Companion.uniformNDFromRanges(
|
public fun <V : Any, A : Field<V>> Histogram.Companion.uniformNDFromRanges(
|
||||||
valueAlgebra: FieldOpsND<V, A>,
|
valueAlgebraND: FieldOpsND<V, A>,
|
||||||
vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>,
|
vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>,
|
||||||
bufferFactory: BufferFactory<V> = Buffer.Companion::boxing,
|
bufferFactory: BufferFactory<V> = Buffer.Companion::boxing,
|
||||||
): UniformHistogramGroupND<V, A> = UniformHistogramGroupND(
|
): UniformHistogramGroupND<V, A> = UniformHistogramGroupND(
|
||||||
valueAlgebra,
|
valueAlgebraND,
|
||||||
ListBuffer(
|
ListBuffer(
|
||||||
ranges
|
ranges
|
||||||
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
|
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
|
||||||
|
@ -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 <B : ClosedRange<Double>> TreeMap<Double, B>.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<Double, ValueAndError>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A histogram based on a tree map of values
|
||||||
|
*/
|
||||||
|
public class TreeHistogram<V : Any>(
|
||||||
|
private val binMap: TreeMap<Double, Bin1D<Double, V>>,
|
||||||
|
) : Histogram1D<Double, V> {
|
||||||
|
override fun get(value: Double): Bin1D<Double, V>? = binMap.getBin(value)
|
||||||
|
override val bins: Collection<Bin1D<Double, V>> get() = binMap.values
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A space for univariate histograms with variable bin borders based on a tree map
|
||||||
|
*/
|
||||||
|
public class TreeHistogramGroup<V : Any, A>(
|
||||||
|
public val valueAlgebra: A,
|
||||||
|
@PublishedApi internal val binFactory: (Double) -> DoubleDomain1D,
|
||||||
|
) : Group<TreeHistogram<V>>, ScaleOperations<TreeHistogram<V>> where A : Ring<V>, A : ScaleOperations<V> {
|
||||||
|
|
||||||
|
internal inner class DomainCounter(val domain: DoubleDomain1D, val counter: Counter<V> = Counter.of(valueAlgebra)) :
|
||||||
|
ClosedRange<Double> by domain.range
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal inner class TreeHistogramBuilder : Histogram1DBuilder<Double, V> {
|
||||||
|
|
||||||
|
override val defaultValue: V get() = valueAlgebra.one
|
||||||
|
|
||||||
|
private val bins: TreeMap<Double, DomainCounter> = 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<V> {
|
||||||
|
val map = bins.mapValuesTo(TreeMap<Double, Bin1D<Double, V>>()) { (_, binCounter) ->
|
||||||
|
Bin1D(binCounter.domain, binCounter.counter.value)
|
||||||
|
}
|
||||||
|
return TreeHistogram(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun produce(block: Histogram1DBuilder<Double, V>.() -> Unit): TreeHistogram<V> =
|
||||||
|
TreeHistogramBuilder().apply(block).build()
|
||||||
|
|
||||||
|
override fun add(
|
||||||
|
left: TreeHistogram<V>,
|
||||||
|
right: TreeHistogram<V>,
|
||||||
|
): TreeHistogram<V> {
|
||||||
|
val bins = TreeMap<Double, Bin1D<Double, V>>().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<V>, value: Double): TreeHistogram<V> {
|
||||||
|
val bins = TreeMap<Double, Bin1D<Double, V>>().apply {
|
||||||
|
a.bins.forEach { bin ->
|
||||||
|
put(
|
||||||
|
bin.domain.center,
|
||||||
|
Bin1D(bin.domain, valueAlgebra.scale(bin.binValue, value))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TreeHistogram(bins)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun TreeHistogram<V>.unaryMinus(): TreeHistogram<V> = this * (-1)
|
||||||
|
|
||||||
|
override val zero: TreeHistogram<V> = produce { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * Build and fill a histogram with custom borders. Returns a read-only histogram.
|
||||||
|
// */
|
||||||
|
//public inline fun Histogram.custom(
|
||||||
|
// borders: DoubleArray,
|
||||||
|
// builder: Histogram1DBuilder<Double, Double>.() -> 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 <V : Any, A> Histogram.Companion.custom1D(
|
||||||
|
valueAlgebra: A,
|
||||||
|
borders: Buffer<Double>,
|
||||||
|
): TreeHistogramGroup<V, A> where A : Ring<V>, A : ScaleOperations<V> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <B : ClosedRange<Double>> TreeMap<Double, B>.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<Double, ValueAndError>
|
|
||||||
|
|
||||||
public class TreeHistogram(
|
|
||||||
private val binMap: TreeMap<Double, WeightedBin1D>,
|
|
||||||
) : Histogram1D<Double, ValueAndError> {
|
|
||||||
override fun get(value: Double): WeightedBin1D? = binMap.getBin(value)
|
|
||||||
override val bins: Collection<WeightedBin1D> get() = binMap.values
|
|
||||||
}
|
|
||||||
|
|
||||||
@PublishedApi
|
|
||||||
internal class TreeHistogramBuilder(val binFactory: (Double) -> DoubleDomain1D) : Histogram1DBuilder<Double, Double> {
|
|
||||||
|
|
||||||
override val defaultValue: Double get() = 1.0
|
|
||||||
|
|
||||||
internal class BinCounter(val domain: DoubleDomain1D, val counter: Counter<Double> = Counter.ofDouble()) :
|
|
||||||
ClosedRange<Double> by domain.range
|
|
||||||
|
|
||||||
private val bins: TreeMap<Double, BinCounter> = 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<Double>, 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<Double, WeightedBin1D>()) { (_, 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<TreeHistogram>, ScaleOperations<TreeHistogram> {
|
|
||||||
|
|
||||||
public inline fun fill(block: Histogram1DBuilder<Double, Double>.() -> 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<Double, WeightedBin1D>().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<Double, WeightedBin1D>().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<Double, Double>.() -> Unit,
|
|
||||||
): TreeHistogram = uniform(binSize, start).fill(builder)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build and fill a histogram with custom borders. Returns a read-only histogram.
|
|
||||||
*/
|
|
||||||
public inline fun custom(
|
|
||||||
borders: DoubleArray,
|
|
||||||
builder: Histogram1DBuilder<Double, Double>.() -> Unit,
|
|
||||||
): 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,19 +6,24 @@
|
|||||||
package space.kscience.kmath.histogram
|
package space.kscience.kmath.histogram
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import space.kscience.kmath.operations.DoubleField
|
||||||
|
import space.kscience.kmath.real.step
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class TreeHistogramTest {
|
class TreeHistogramTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun normalFill() {
|
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) {
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user