Complete refactor of histograms API #476

Merged
altavir merged 15 commits from refactor/histogram into dev 2022-04-10 20:59:05 +03:00
8 changed files with 71 additions and 54 deletions
Showing only changes of commit 39640498fc - Show all commits

View File

@ -11,7 +11,7 @@ allprojects {
}
group = "space.kscience"
version = "0.3.0-dev-20"
version = "0.3.0-dev-21"
}
subprojects {

View File

@ -5,4 +5,4 @@
kotlin.code.style=official
toolsVersion=0.11.1-kotlin-1.6.10
toolsVersion=0.11.2-kotlin-1.6.10

View File

@ -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<Double>, private val upper: Buffer<Double>) : DoubleDomain {
public class HyperSquareDomain(public val lower: Buffer<Double>, public val upper: Buffer<Double>) : 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<Double>): Boolean = point.indices.all { i ->
point[i] in lower[i]..upper[i]
}

View File

@ -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<Double>,
private val upper: Buffer<Double>,
@ -47,7 +49,7 @@ public class DoubleHistogramSpace(
}
@OptIn(UnstableKMathAPI::class)
override fun getDomain(index: IntArray): Domain<Double> {
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<Double>.domain: HyperSquareDomain
get() = (this as? DomainBin<Double>)?.domain as? HyperSquareDomain
?: error("Im a teapot. This is not my bin")
override fun produceBin(index: IntArray, value: Double): Bin<Double> {
@OptIn(UnstableKMathAPI::class)
override fun produceBin(index: IntArray, value: Double): DomainBin<Double> {
val domain = getDomain(index)
return DomainBin(domain, value)
}
@ -96,7 +103,9 @@ public class DoubleHistogramSpace(
*)
*```
*/
public fun fromRanges(vararg ranges: ClosedFloatingPointRange<Double>): DoubleHistogramSpace = DoubleHistogramSpace(
public fun fromRanges(
vararg ranges: ClosedFloatingPointRange<Double>,
): DoubleHistogramSpace = DoubleHistogramSpace(
ranges.map(ClosedFloatingPointRange<Double>::start).asBuffer(),
ranges.map(ClosedFloatingPointRange<Double>::endInclusive).asBuffer()
)
@ -110,21 +119,22 @@ public class DoubleHistogramSpace(
*)
*```
*/
public fun fromRanges(vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>): DoubleHistogramSpace =
DoubleHistogramSpace(
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::start)
),
public fun fromRanges(
vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>,
): DoubleHistogramSpace = DoubleHistogramSpace(
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::start)
),
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::endInclusive)
),
ListBuffer(
ranges
.map(Pair<ClosedFloatingPointRange<Double>, Int>::first)
.map(ClosedFloatingPointRange<Double>::endInclusive)
),
ranges.map(Pair<ClosedFloatingPointRange<Double>, Int>::second).toIntArray()
)
ranges.map(Pair<ClosedFloatingPointRange<Double>, Int>::second).toIntArray()
)
}
}

View File

@ -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<in T : Comparable<T>>(
override val value: Number,
) : Bin<T>, Domain<T> by domain
@OptIn(UnstableKMathAPI::class)
public class IndexedHistogram<T : Comparable<T>, V : Any>(
public val context: IndexedHistogramSpace<T, V>,
public val histogramSpace: IndexedHistogramSpace<T, V>,
public val values: StructureND<V>,
) : Histogram<T, Bin<T>> {
override fun get(point: Point<T>): Bin<T>? {
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<Bin<T>>
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<T : Comparable<T>, V : Any>
public fun produce(builder: HistogramBuilder<T>.() -> Unit): IndexedHistogram<T, V>
override fun add(left: IndexedHistogram<T, V>, right: IndexedHistogram<T, V>): IndexedHistogram<T, V> {
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<T, V>, value: Double): IndexedHistogram<T, V> {
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 })
}

View File

@ -37,26 +37,6 @@ public class UnivariateBin(
public interface UnivariateHistogram : Histogram<Double, UnivariateBin> {
public operator fun get(value: Double): UnivariateBin?
override operator fun get(point: Buffer<Double>): 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

View File

@ -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.
*/

View File

@ -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())
}