Histograms refactor

This commit is contained in:
Alexander Nozik 2021-02-12 22:43:35 +03:00
parent ee29b218f0
commit 987997cc02
15 changed files with 267 additions and 216 deletions

View File

@ -41,6 +41,7 @@
- Refactor histograms. They are marked as prototype - Refactor histograms. They are marked as prototype
- `Complex` and related features moved to a separate module `kmath-complex` - `Complex` and related features moved to a separate module `kmath-complex`
- Refactor AlgebraElement - Refactor AlgebraElement
- Add `out` projection to `Buffer` generic
### Deprecated ### Deprecated

View File

@ -76,6 +76,14 @@ benchmark {
iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds
include("DotBenchmark") include("DotBenchmark")
} }
configurations.register("expressions") {
warmups = 1 // number of warmup iterations
iterations = 3 // number of iterations
iterationTime = 500 // time in seconds per iteration
iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds
include("ExpressionsInterpretersBenchmark")
}
} }
kotlin.sourceSets.all { kotlin.sourceSets.all {

View File

@ -21,8 +21,17 @@ public class MstExpression<T, out A : Algebra<T>>(public val algebra: A, public
null null
} ?: arguments.getValue(StringSymbol(value)) } ?: arguments.getValue(StringSymbol(value))
override fun unaryOperationFunction(operation: String): (arg: T) -> T = algebra.unaryOperationFunction(operation) override fun unaryOperation(operation: String, arg: T): T =
override fun binaryOperationFunction(operation: String): (left: T, right: T) -> T = algebra.binaryOperationFunction(operation) algebra.unaryOperation(operation, arg)
override fun binaryOperation(operation: String, left: T, right: T): T =
algebra.binaryOperation(operation, left, right)
override fun unaryOperationFunction(operation: String): (arg: T) -> T =
algebra.unaryOperationFunction(operation)
override fun binaryOperationFunction(operation: String): (left: T, right: T) -> T =
algebra.binaryOperationFunction(operation)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun number(value: Number): T = if (algebra is NumericAlgebra<*>) override fun number(value: Number): T = if (algebra is NumericAlgebra<*>)

View File

@ -4,7 +4,7 @@ public abstract interface class kscience/kmath/domains/Domain {
} }
public final class kscience/kmath/domains/HyperSquareDomain : kscience/kmath/domains/RealDomain { public final class kscience/kmath/domains/HyperSquareDomain : kscience/kmath/domains/RealDomain {
public synthetic fun <init> ([D[DLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lkscience/kmath/structures/Buffer;Lkscience/kmath/structures/Buffer;)V
public fun contains (Lkscience/kmath/structures/Buffer;)Z public fun contains (Lkscience/kmath/structures/Buffer;)Z
public fun getDimension ()I public fun getDimension ()I
public fun getLowerBound (I)Ljava/lang/Double; public fun getLowerBound (I)Ljava/lang/Double;

View File

@ -16,6 +16,7 @@
package kscience.kmath.domains package kscience.kmath.domains
import kscience.kmath.linear.Point import kscience.kmath.linear.Point
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.RealBuffer import kscience.kmath.structures.RealBuffer
import kscience.kmath.structures.indices import kscience.kmath.structures.indices
@ -25,20 +26,20 @@ import kscience.kmath.structures.indices
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
public class HyperSquareDomain(private val lower: RealBuffer, private val upper: RealBuffer) : RealDomain { public class HyperSquareDomain(private val lower: Buffer<Double>, private val upper: Buffer<Double>) : RealDomain {
public override val dimension: Int get() = lower.size public override val dimension: Int get() = lower.size
public override operator fun contains(point: Point<Double>): Boolean = point.indices.all { i -> public override operator fun contains(point: Point<Double>): Boolean = point.indices.all { i ->
point[i] in lower[i]..upper[i] point[i] in lower[i]..upper[i]
} }
public override fun getLowerBound(num: Int, point: Point<Double>): Double? = lower[num] public override fun getLowerBound(num: Int, point: Point<Double>): Double = lower[num]
public override fun getLowerBound(num: Int): Double? = lower[num] public override fun getLowerBound(num: Int): Double = lower[num]
public override fun getUpperBound(num: Int, point: Point<Double>): Double? = upper[num] public override fun getUpperBound(num: Int, point: Point<Double>): Double = upper[num]
public override fun getUpperBound(num: Int): Double? = upper[num] public override fun getUpperBound(num: Int): Double = upper[num]
public override fun nearestInDomain(point: Point<Double>): Point<Double> { public override fun nearestInDomain(point: Point<Double>): Point<Double> {
val res = DoubleArray(point.size) { i -> val res = DoubleArray(point.size) { i ->

View File

@ -78,8 +78,7 @@ public interface NDStructure<T> {
strides: Strides, strides: Strides,
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing, bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
initializer: (IntArray) -> T, initializer: (IntArray) -> T,
): NDBuffer<T> = ): NDBuffer<T> = NDBuffer(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) })
NDBuffer(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) })
/** /**
* Inline create NDStructure with non-boxing buffer implementation if it is possible * Inline create NDStructure with non-boxing buffer implementation if it is possible
@ -87,15 +86,13 @@ public interface NDStructure<T> {
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
strides: Strides, strides: Strides,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): NDBuffer<T> = ): NDBuffer<T> = NDBuffer(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) })
NDBuffer(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) })
public inline fun <T : Any> auto( public inline fun <T : Any> auto(
type: KClass<T>, type: KClass<T>,
strides: Strides, strides: Strides,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): NDBuffer<T> = ): NDBuffer<T> = NDBuffer(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) })
NDBuffer(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) })
public fun <T> build( public fun <T> build(
shape: IntArray, shape: IntArray,
@ -106,8 +103,7 @@ public interface NDStructure<T> {
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
shape: IntArray, shape: IntArray,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): NDBuffer<T> = ): NDBuffer<T> = auto(DefaultStrides(shape), initializer)
auto(DefaultStrides(shape), initializer)
@JvmName("autoVarArg") @JvmName("autoVarArg")
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
@ -120,8 +116,7 @@ public interface NDStructure<T> {
type: KClass<T>, type: KClass<T>,
vararg shape: Int, vararg shape: Int,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): NDBuffer<T> = ): NDBuffer<T> = auto(type, DefaultStrides(shape), initializer)
auto(type, DefaultStrides(shape), initializer)
} }
} }

View File

@ -21,7 +21,7 @@ public typealias MutableBufferFactory<T> = (Int, (Int) -> T) -> MutableBuffer<T>
* *
* @param T the type of elements contained in the buffer. * @param T the type of elements contained in the buffer.
*/ */
public interface Buffer<T> { public interface Buffer<out T> {
/** /**
* The size of this buffer. * The size of this buffer.
*/ */

View File

@ -2,16 +2,18 @@ package kscience.kmath.histogram
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.getAndUpdate import kotlinx.atomicfu.getAndUpdate
import kscience.kmath.operations.RealField
import kscience.kmath.operations.Space import kscience.kmath.operations.Space
/* /**
* Common representation for atomic counters * Common representation for atomic counters
* TODO replace with atomics
*/ */
public interface Counter<T : Any> { public interface Counter<T : Any> {
public fun add(delta: T) public fun add(delta: T)
public val value: T public val value: T
public companion object{
public fun real(): ObjectCounter<Double> = ObjectCounter(RealField)
}
} }
public class IntCounter : Counter<Int> { public class IntCounter : Counter<Int> {

View File

@ -13,8 +13,6 @@ public interface Bin<T : Any> : Domain<T> {
* The value of this bin. * The value of this bin.
*/ */
public val value: Number public val value: Number
public val center: Point<T>
} }
public interface Histogram<T : Any, out B : Bin<T>> { public interface Histogram<T : Any, out B : Bin<T>> {
@ -28,29 +26,30 @@ public interface Histogram<T : Any, out B : Bin<T>> {
*/ */
public val dimension: Int public val dimension: Int
public val bins: Collection<B> public val bins: Iterable<B>
} }
public interface HistogramBuilder<T : Any, out B : Bin<T>> : Histogram<T, B> { public fun interface HistogramBuilder<T : Any> {
/** /**
* Increment appropriate bin * Increment appropriate bin
*/ */
public fun putWithWeight(point: Point<out T>, weight: Double) public fun putValue(point: Point<out T>, value: Number)
public fun put(point: Point<out T>): Unit = putWithWeight(point, 1.0)
} }
public fun <T : Any> HistogramBuilder<T, *>.put(vararg point: T): Unit = put(ArrayBuffer(point)) public fun <T : Any, B : Bin<T>> HistogramBuilder<T>.put(point: Point<out T>): Unit = putValue(point, 1.0)
public fun HistogramBuilder<Double, *>.put(vararg point: Number): Unit = public fun <T : Any> HistogramBuilder<T>.put(vararg point: T): Unit = put(ArrayBuffer(point))
public fun HistogramBuilder<Double>.put(vararg point: Number): Unit =
put(RealBuffer(point.map { it.toDouble() }.toDoubleArray())) put(RealBuffer(point.map { it.toDouble() }.toDoubleArray()))
public fun HistogramBuilder<Double, *>.put(vararg point: Double): Unit = put(RealBuffer(point)) public fun HistogramBuilder<Double>.put(vararg point: Double): Unit = put(RealBuffer(point))
public fun <T : Any> HistogramBuilder<T, *>.fill(sequence: Iterable<Point<T>>): Unit = sequence.forEach { put(it) } public fun <T : Any> HistogramBuilder<T>.fill(sequence: Iterable<Point<T>>): Unit = sequence.forEach { put(it) }
/** /**
* Pass a sequence builder into histogram * Pass a sequence builder into histogram
*/ */
public fun <T : Any> HistogramBuilder<T, *>.fill(block: suspend SequenceScope<Point<T>>.() -> Unit): Unit = public fun <T : Any> HistogramBuilder<T>.fill(block: suspend SequenceScope<Point<T>>.() -> Unit): Unit =
fill(sequence(block).asIterable()) fill(sequence(block).asIterable())

View File

@ -0,0 +1,76 @@
package kscience.kmath.histogram
import kscience.kmath.domains.Domain
import kscience.kmath.linear.Point
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.nd.NDSpace
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.Strides
import kscience.kmath.operations.Space
import kscience.kmath.operations.SpaceElement
import kscience.kmath.operations.invoke
/**
* A simple histogram bin based on domain
*/
public data class DomainBin<T : Comparable<T>>(
public val domain: Domain<T>,
public override val value: Number,
) : Bin<T>, Domain<T> by domain
@OptIn(UnstableKMathAPI::class)
public class IndexedHistogram<T : Comparable<T>, V : Any>(
override val context: IndexedHistogramSpace<T, V>,
public val values: NDStructure<V>,
) : Histogram<T, Bin<T>>, SpaceElement<IndexedHistogram<T, V>, IndexedHistogramSpace<T, V>> {
override fun get(point: Point<T>): Bin<T>? {
val index = context.getIndex(point) ?: return null
return context.produceBin(index, values[index])
}
override val dimension: Int get() = context.strides.shape.size
override val bins: Iterable<Bin<T>>
get() = context.strides.indices().map {
context.produceBin(it, values[it])
}.asIterable()
}
/**
* A space for producing histograms with values in a NDStructure
*/
public interface IndexedHistogramSpace<T : Comparable<T>, V : Any> : Space<IndexedHistogram<T, V>> {
//public val valueSpace: Space<V>
public val strides: Strides
public val histogramValueSpace: NDSpace<V, *> //= NDAlgebra.space(valueSpace, Buffer.Companion::boxing, *shape),
/**
* Resolve index of the bin including given [point]
*/
public fun getIndex(point: Point<T>): IntArray?
/**
* Get a bin domain represented by given index
*/
public fun getDomain(index: IntArray): Domain<T>?
public fun produceBin(index: IntArray, value: V): Bin<T>
public fun produce(builder: HistogramBuilder<T>.() -> Unit): IndexedHistogram<T, V>
override fun add(a: IndexedHistogram<T, V>, b: IndexedHistogram<T, V>): IndexedHistogram<T, V> {
require(a.context == this) { "Can't operate on a histogram produced by external space" }
require(b.context == this) { "Can't operate on a histogram produced by external space" }
return IndexedHistogram(this, histogramValueSpace.invoke { a.values + b.values })
}
override fun multiply(a: IndexedHistogram<T, V>, k: Number): IndexedHistogram<T, V> {
require(a.context == this) { "Can't operate on a histogram produced by external space" }
return IndexedHistogram(this, histogramValueSpace.invoke { a.values * k })
}
override val zero: IndexedHistogram<T, V> get() = produce { }
}

View File

@ -1,166 +0,0 @@
package kscience.kmath.histogram
import kscience.kmath.linear.Point
import kscience.kmath.nd.DefaultStrides
import kscience.kmath.nd.NDStructure
import kscience.kmath.operations.SpaceOperations
import kscience.kmath.operations.invoke
import kscience.kmath.structures.*
import kotlin.math.floor
public data class MultivariateBinDefinition<T : Comparable<T>>(
public val space: SpaceOperations<Point<T>>,
public val center: Point<T>,
public val sizes: Point<T>,
) {
public fun contains(vector: Point<out T>): Boolean {
require(vector.size == center.size) { "Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}" }
val upper = space { center + sizes / 2.0 }
val lower = space { center - sizes / 2.0 }
return vector.asSequence().mapIndexed { i, value -> value in lower[i]..upper[i] }.all { it }
}
}
public class MultivariateBin<T : Comparable<T>>(
public val definition: MultivariateBinDefinition<T>,
public val count: Long,
public override val value: Double,
) : Bin<T> {
public override val dimension: Int
get() = definition.center.size
public override val center: Point<T>
get() = definition.center
public override operator fun contains(point: Point<T>): Boolean = definition.contains(point)
}
/**
* Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions.
*/
public class RealHistogram(
private val lower: Buffer<Double>,
private val upper: Buffer<Double>,
private val binNums: IntArray = IntArray(lower.size) { 20 },
) : HistogramBuilder<Double, MultivariateBin<Double>> {
private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 })
private val counts: NDStructure<LongCounter> = NDStructure.auto(strides) { LongCounter() }
private val values: NDStructure<DoubleCounter> = NDStructure.auto(strides) { DoubleCounter() }
public override val dimension: Int get() = lower.size
private val binSize = RealBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] }
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(!(0 until dimension).any { upper[it] - lower[it] < 0 }) { "Range for one of axis is not strictly positive" }
}
/**
* Get internal [NDStructure] 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() + 1
}
private fun getIndex(point: Buffer<out Double>): IntArray = IntArray(dimension) { getIndex(it, point[it]) }
private fun getCount(index: IntArray): Long = counts[index].sum()
public fun getCount(point: Buffer<out Double>): Long = getCount(getIndex(point))
private fun getValue(index: IntArray): Double = values[index].sum()
public fun getValue(point: Buffer<out Double>): Double = getValue(getIndex(point))
private fun getBinDefinition(index: IntArray): MultivariateBinDefinition<Double> {
val center = index.mapIndexed { axis, i ->
when (i) {
0 -> Double.NEGATIVE_INFINITY
strides.shape[axis] - 1 -> Double.POSITIVE_INFINITY
else -> lower[axis] + (i.toDouble() - 0.5) * binSize[axis]
}
}.asBuffer()
return MultivariateBinDefinition(RealBufferFieldOperations, center, binSize)
}
public fun getBinDefinition(point: Buffer<out Double>): MultivariateBinDefinition<Double> =
getBinDefinition(getIndex(point))
public override operator fun get(point: Buffer<out Double>): MultivariateBin<Double>? {
val index = getIndex(point)
return MultivariateBin(getBinDefinition(index), getCount(index), getValue(index))
}
// fun put(point: Point<out Double>){
// val index = getIndex(point)
// values[index].increment()
// }
public override fun putWithWeight(point: Buffer<out Double>, weight: Double) {
val index = getIndex(point)
counts[index].increment()
values[index].add(weight)
}
override val bins: Collection<MultivariateBin<Double>>
get() = strides.indices().map { index ->
MultivariateBin(getBinDefinition(index), counts[index].sum(), values[index].sum())
}.toList()
/**
* NDStructure containing number of events in bins without weights
*/
public fun counts(): NDStructure<Long> = NDStructure.auto(counts.shape) { counts[it].sum() }
/**
* NDStructure containing values of bins including weights
*/
public fun values(): NDStructure<Double> = NDStructure.auto(values.shape) { values[it].sum() }
public companion object {
/**
* Use it like
* ```
*FastHistogram.fromRanges(
* (-1.0..1.0),
* (-1.0..1.0)
*)
*```
*/
public fun fromRanges(vararg ranges: ClosedFloatingPointRange<Double>): RealHistogram = RealHistogram(
ranges.map(ClosedFloatingPointRange<Double>::start).asBuffer(),
ranges.map(ClosedFloatingPointRange<Double>::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<ClosedFloatingPointRange<Double>, Int>): RealHistogram =
RealHistogram(
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::start)
),
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::endInclusive)
),
ranges.map(Pair<ClosedFloatingPointRange<Double>, Int>::second).toIntArray()
)
}
}

View File

@ -0,0 +1,121 @@
package kscience.kmath.histogram
import kscience.kmath.domains.Domain
import kscience.kmath.domains.HyperSquareDomain
import kscience.kmath.nd.*
import kscience.kmath.structures.*
import kotlin.math.floor
public class RealHistogramSpace(
private val lower: Buffer<Double>,
private val upper: Buffer<Double>,
private val binNums: IntArray = IntArray(lower.size) { 20 },
) : IndexedHistogramSpace<Double, Double> {
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
private val shape = IntArray(binNums.size) { binNums[it] + 2 }
override val histogramValueSpace: RealNDField = NDAlgebra.real(*shape)
override val strides: Strides get() = histogramValueSpace.strides
private val binSize = RealBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] }
/**
* Get internal [NDStructure] 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 getIndex(point: Buffer<Double>): IntArray = IntArray(dimension) {
getIndex(it, point[it])
}
override fun getDomain(index: IntArray): Domain<Double> {
val lowerBoundary = index.mapIndexed { axis, i ->
when (i) {
0 -> Double.NEGATIVE_INFINITY
strides.shape[axis] - 1 -> upper[axis]
else -> lower[axis] + (i.toDouble()) * binSize[axis]
}
}.asBuffer()
val upperBoundary = index.mapIndexed { axis, i ->
when (i) {
0 -> lower[axis]
strides.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: Double): Bin<Double> {
val domain = getDomain(index)
return DomainBin(domain, value)
}
override fun produce(builder: HistogramBuilder<Double>.() -> Unit): IndexedHistogram<Double, Double> {
val ndCounter = NDStructure.auto(strides) { Counter.real() }
val hBuilder = HistogramBuilder<Double> { point, value ->
val index = getIndex(point)
ndCounter[index].add(1.0)
}
hBuilder.apply(builder)
val values: NDBuffer<Double> = ndCounter.mapToBuffer { it.value }
return IndexedHistogram(this, values)
}
public companion object{
/**
* Use it like
* ```
*FastHistogram.fromRanges(
* (-1.0..1.0),
* (-1.0..1.0)
*)
*```
*/
public fun fromRanges(vararg ranges: ClosedFloatingPointRange<Double>): RealHistogramSpace = RealHistogramSpace(
ranges.map(ClosedFloatingPointRange<Double>::start).asBuffer(),
ranges.map(ClosedFloatingPointRange<Double>::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<ClosedFloatingPointRange<Double>, Int>): RealHistogramSpace =
RealHistogramSpace(
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::start)
),
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::endInclusive)
),
ranges.map(Pair<ClosedFloatingPointRange<Double>, Int>::second).toIntArray()
)
}
}

View File

@ -1,6 +1,6 @@
package scietifik.kmath.histogram package scietifik.kmath.histogram
import kscience.kmath.histogram.RealHistogram import kscience.kmath.histogram.RealHistogramSpace
import kscience.kmath.histogram.fill import kscience.kmath.histogram.fill
import kscience.kmath.histogram.put import kscience.kmath.histogram.put
import kscience.kmath.real.RealVector import kscience.kmath.real.RealVector
@ -11,11 +11,13 @@ import kotlin.test.*
internal class MultivariateHistogramTest { internal class MultivariateHistogramTest {
@Test @Test
fun testSinglePutHistogram() { fun testSinglePutHistogram() {
val histogram = RealHistogram.fromRanges( val hSpace = RealHistogramSpace.fromRanges(
(-1.0..1.0), (-1.0..1.0),
(-1.0..1.0) (-1.0..1.0)
) )
histogram.put(0.55, 0.55) 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.value.toInt() > 0 } ?: fail()
assertTrue { bin.contains(RealVector(0.55, 0.55)) } assertTrue { bin.contains(RealVector(0.55, 0.55)) }
assertTrue { bin.contains(RealVector(0.6, 0.5)) } assertTrue { bin.contains(RealVector(0.6, 0.5)) }
@ -24,7 +26,7 @@ internal class MultivariateHistogramTest {
@Test @Test
fun testSequentialPut() { fun testSequentialPut() {
val histogram = RealHistogram.fromRanges( val hSpace = RealHistogramSpace.fromRanges(
(-1.0..1.0), (-1.0..1.0),
(-1.0..1.0), (-1.0..1.0),
(-1.0..1.0) (-1.0..1.0)
@ -34,10 +36,11 @@ internal class MultivariateHistogramTest {
fun nextDouble() = random.nextDouble(-1.0, 1.0) fun nextDouble() = random.nextDouble(-1.0, 1.0)
val n = 10000 val n = 10000
val histogram = hSpace.produce {
histogram.fill { fill {
repeat(n) { repeat(n) {
yield(RealVector(nextDouble(), nextDouble(), nextDouble())) yield(RealVector(nextDouble(), nextDouble(), nextDouble()))
}
} }
} }
assertEquals(n, histogram.bins.sumBy { it.value.toInt() }) assertEquals(n, histogram.bins.sumBy { it.value.toInt() })

View File

@ -30,7 +30,7 @@ public interface UnivariateBin : Bin<Double> {
*/ */
public val standardDeviation: Double public val standardDeviation: Double
public override val center: Point<Double> get() = doubleArrayOf(position).asBuffer() public val center: Point<Double> get() = doubleArrayOf(position).asBuffer()
public override val dimension: Int get() = 1 public override val dimension: Int get() = 1
@ -44,7 +44,7 @@ public operator fun UnivariateBin.contains(value: Double): Boolean =
public interface UnivariateHistogram : Histogram<Double, UnivariateBin>, public interface UnivariateHistogram : Histogram<Double, UnivariateBin>,
SpaceElement<UnivariateHistogram, UnivariateHistogramSpace> { SpaceElement<UnivariateHistogram, UnivariateHistogramSpace> {
public operator fun get(value: Double): UnivariateBin? public operator fun get(value: Double): UnivariateBin?
public override operator fun get(point: Buffer<out Double>): UnivariateBin? = get(point[0]) public override operator fun get(point: Buffer<Double>): UnivariateBin? = get(point[0])
public companion object { public companion object {
/** /**
@ -67,12 +67,14 @@ public interface UnivariateHistogram : Histogram<Double, UnivariateBin>,
} }
} }
public interface UnivariateHistogramBuilder { public interface UnivariateHistogramBuilder: HistogramBuilder<Double> {
/** /**
* Thread safe put operation * Thread safe put operation
*/ */
public fun put(value: Double, weight: Double = 1.0) public fun put(value: Double, weight: Double = 1.0)
public fun putWithWeight(point: Buffer<out Double>, weight: Double)
override fun putValue(point: Buffer<Double>, value: Number)
/** /**
* Put several items into a single bin * Put several items into a single bin

View File

@ -31,19 +31,19 @@ private class UnivariateBinCounter(
override val def: UnivariateHistogramBinDefinition, override val def: UnivariateHistogramBinDefinition,
) : UnivariateBin { ) : UnivariateBin {
val counter: LongCounter = LongCounter() val counter: LongCounter = LongCounter()
val valueCounter: DoubleCounter = DoubleCounter() val valueCounter: ObjectCounter<Double> = Counter.real()
/** /**
* The precise number of events ignoring weighting * The precise number of events ignoring weighting
*/ */
val count: Long get() = counter.sum() val count: Long get() = counter.value
override val standardDeviation: Double get() = sqrt(count.toDouble()) / count * value override val standardDeviation: Double get() = sqrt(count.toDouble()) / count * value
/** /**
* The value of histogram including weighting * The value of histogram including weighting
*/ */
override val value: Double get() = valueCounter.sum() override val value: Double get() = valueCounter.value
public fun increment(count: Long, value: Double) { public fun increment(count: Long, value: Double) {
counter.add(count) counter.add(count)
@ -83,8 +83,8 @@ public class UnivariateHistogramSpace(
} }
} }
override fun putWithWeight(point: Buffer<out Double>, weight: Double) { override fun putValue(point: Buffer<Double>, value: Number) {
put(point[0], weight) put(point[0], value.toDouble())
} }
/** /**