Buffer views

This commit is contained in:
Alexander Nozik 2021-11-08 17:50:49 +03:00
parent bf504ae6c5
commit a1351aa942
55 changed files with 610 additions and 176 deletions

View File

@ -12,8 +12,6 @@ import space.kscience.kmath.commons.optimization.CMOptimizer
import space.kscience.kmath.distributions.NormalDistribution
import space.kscience.kmath.expressions.chiSquaredExpression
import space.kscience.kmath.expressions.symbol
import space.kscience.kmath.operations.asIterable
import space.kscience.kmath.operations.toList
import space.kscience.kmath.optimization.FunctionOptimizationTarget
import space.kscience.kmath.optimization.optimizeWith
import space.kscience.kmath.optimization.resultPoint
@ -22,6 +20,8 @@ import space.kscience.kmath.real.DoubleVector
import space.kscience.kmath.real.map
import space.kscience.kmath.real.step
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.structures.asIterable
import space.kscience.kmath.structures.toList
import space.kscience.plotly.*
import space.kscience.plotly.models.ScatterMode
import space.kscience.plotly.models.TraceValues

View File

@ -13,8 +13,6 @@ import space.kscience.kmath.distributions.NormalDistribution
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.expressions.binding
import space.kscience.kmath.expressions.symbol
import space.kscience.kmath.operations.asIterable
import space.kscience.kmath.operations.toList
import space.kscience.kmath.optimization.QowOptimizer
import space.kscience.kmath.optimization.chiSquaredOrNull
import space.kscience.kmath.optimization.fitWith
@ -22,6 +20,8 @@ import space.kscience.kmath.optimization.resultPoint
import space.kscience.kmath.real.map
import space.kscience.kmath.real.step
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.structures.asIterable
import space.kscience.kmath.structures.toList
import space.kscience.plotly.*
import space.kscience.plotly.models.ScatterMode
import kotlin.math.abs

View File

@ -1,37 +1,43 @@
package space.kscience.kmath.series
import net.jafama.StrictFastMath.abs
import kotlinx.html.FlowContent
import kotlinx.html.h1
import space.kscience.kmath.operations.algebra
import space.kscience.kmath.operations.bufferAlgebra
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.operations.toList
import space.kscience.kmath.structures.Buffer
import space.kscience.plotly.Plotly
import space.kscience.plotly.makeFile
import space.kscience.plotly.scatter
import space.kscience.kmath.structures.toList
import space.kscience.plotly.*
import kotlin.math.PI
import kotlin.math.max
fun main() = Double.algebra.bufferAlgebra.seriesAlgebra(0..100).invoke {
fun Buffer<Double>.plot() {
val ls = labels
Plotly.plot {
fun main() = with(Double.algebra.bufferAlgebra.seriesAlgebra()) {
fun FlowContent.plotSeries(buffer: Buffer<Double>) {
val ls = buffer.labels
plot {
scatter {
x.numbers = ls
y.numbers = toList()
y.numbers = buffer.toList()
}
}.makeFile()
layout {
xaxis {
range(0.0..100.0)
}
}
}
}
val s1 = series(100) { sin(2 * PI * it / 100) }
val s2 = series(100) { 1.0 }
val s1 = series(100) { sin(2 * PI * it / 100) + 1.0 }
val s2 = s1.slice(20..50).moveTo(40)
(s1 - s2).plot()
// Kolmogorov-Smirnov test statistic
val kst = (s1 - s2).fold(0.0) { sup, arg -> max(sup, abs(arg))}
val s3: Buffer<Double> = s1.zip(s2) { l, r -> l + r } //s1 + s2
val s4 = ln(s3)
Plotly.page {
h1 { +"This is my plot" }
plotSeries(s1)
plotSeries(s2)
plotSeries(s4)
}.makeFile()
}

View File

@ -7,10 +7,11 @@ package space.kscience.kmath.commons.random
import kotlinx.coroutines.runBlocking
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.samplers.GaussianSampler
import space.kscience.kmath.misc.toIntExact
import space.kscience.kmath.samplers.GaussianSampler
import space.kscience.kmath.samplers.next
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.next
public class CMRandomGeneratorWrapper(
public val factory: (IntArray) -> RandomGenerator,

View File

@ -10,13 +10,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.apache.commons.math3.transform.*
import space.kscience.kmath.complex.Complex
import space.kscience.kmath.operations.SuspendBufferTransform
import space.kscience.kmath.streaming.chunked
import space.kscience.kmath.streaming.spread
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.VirtualBuffer
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.*
/**

View File

@ -13,11 +13,11 @@ import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.Symbol.Companion.y
import space.kscience.kmath.expressions.chiSquaredExpression
import space.kscience.kmath.expressions.symbol
import space.kscience.kmath.operations.map
import space.kscience.kmath.optimization.*
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.map
import kotlin.math.pow
import kotlin.test.Test

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

View File

@ -6,8 +6,9 @@
package space.kscience.kmath.expressions
import space.kscience.kmath.operations.ExtendedField
import space.kscience.kmath.operations.asIterable
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asIterable
import space.kscience.kmath.structures.indices
import kotlin.jvm.JvmName
/**

View File

@ -13,6 +13,7 @@ import space.kscience.kmath.operations.*
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.VirtualBuffer
import space.kscience.kmath.structures.indices
public class BufferedLinearSpace<T, out A : Ring<T>>(

View File

@ -6,10 +6,10 @@
package space.kscience.kmath.nd
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.structures.asMutableBuffer
import space.kscience.kmath.structures.asSequence
import kotlin.jvm.JvmInline
/**

View File

@ -8,7 +8,6 @@ package space.kscience.kmath.operations
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.ShortBuffer
public interface WithSize {
@ -183,7 +182,7 @@ public class BufferField<T, A : Field<T>>(
/**
* Generate full buffer field from given buffer operations
*/
public fun <T, A : Field<T>> BufferFieldOps<T, A>.withSize(size: Int): BufferField<T, A> =
public fun <T, A : Field<T>> BufferAlgebra<T, A>.withSize(size: Int): BufferField<T, A> =
BufferField(elementAlgebra, bufferFactory, size)
//Double buffer specialization
@ -196,6 +195,4 @@ public fun BufferField<Double, *>.buffer(vararg elements: Number): Buffer<Double
public fun <T, A : Field<T>> A.bufferAlgebra(bufferFactory: BufferFactory<T>): BufferFieldOps<T, A> =
BufferFieldOps(this, bufferFactory)
public val DoubleField.bufferAlgebra: BufferFieldOps<Double, DoubleField>
get() = BufferFieldOps(DoubleField, ::DoubleBuffer)
public val DoubleField.bufferAlgebra: DoubleBufferOps get() = DoubleBufferOps

View File

@ -7,10 +7,7 @@ package space.kscience.kmath.operations
import space.kscience.kmath.linear.Point
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.*
import kotlin.math.*

View File

@ -5,7 +5,7 @@
package space.kscience.kmath.structures
import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.operations.WithSize
import kotlin.jvm.JvmInline
import kotlin.reflect.KClass
@ -30,11 +30,11 @@ public typealias MutableBufferFactory<T> = (Int, (Int) -> T) -> MutableBuffer<T>
*
* @param T the type of elements contained in the buffer.
*/
public interface Buffer<out T> {
public interface Buffer<out T> : WithSize {
/**
* The size of this buffer.
*/
public val size: Int
override val size: Int
/**
* Gets element at given index.
@ -44,15 +44,10 @@ public interface Buffer<out T> {
/**
* Iterates over all elements.
*/
public operator fun iterator(): Iterator<T>
public operator fun iterator(): Iterator<T> = indices.asSequence().map(::get).iterator()
override fun toString(): String
/**
* Returns an [IntRange] of the valid indices for this [Buffer].
*/
public val indices: IntRange get() = 0 until size
public companion object {
public fun toString(buffer: Buffer<*>): String =
@ -105,7 +100,12 @@ public interface Buffer<out T> {
}
}
public operator fun<T> Buffer<T>.get(index: UInt): T = get(index.toInt())
/**
* Returns an [IntRange] of the valid indices for this [Buffer].
*/
public val <T> Buffer<T>.indices: IntRange get() = 0 until size
public operator fun <T> Buffer<T>.get(index: UInt): T = get(index.toInt())
/**
* if index is in range of buffer, return the value. Otherwise, return null.

View File

@ -0,0 +1,140 @@
package space.kscience.kmath.structures
import space.kscience.kmath.misc.UnstableKMathAPI
/**
* A buffer that wraps an original buffer
*/
public interface BufferView<T> : Buffer<T> {
public val origin: Buffer<T>
/**
* Get the index in [origin] buffer from index in this buffer.
* Return -1 if element not present in the original buffer
* This method should be used internally to optimize non-boxing access.
*/
@UnstableKMathAPI
public fun originIndex(index: Int): Int
}
/**
* A zero-copy buffer that "sees" only part of original buffer. Slice can't go beyond original buffer borders.
*/
public class BufferSlice<T>(
override val origin: Buffer<T>,
public val offset: UInt = 0U,
override val size: Int,
) : BufferView<T> {
init {
require(size > 0) { "Size must be positive" }
require(offset + size.toUInt() <= origin.size.toUInt()) {
"End of buffer ${offset + size.toUInt()} is beyond the end of origin buffer size ${origin.size}"
}
}
override fun get(index: Int): T = if (index >= size) {
throw IndexOutOfBoundsException("$index is out of ${0 until size} rage")
} else {
origin[index.toUInt() + offset]
}
override fun iterator(): Iterator<T> =
(offset until (offset + size.toUInt())).asSequence().map { origin[it] }.iterator()
@UnstableKMathAPI
override fun originIndex(index: Int): Int = if (index >= size) -1 else index - offset.toInt()
override fun toString(): String = "$origin[$offset..${offset + size.toUInt()}"
}
/**
* An expanded buffer that could include the whole initial buffer ot its part and fills all space beyond it borders with [defaultValue].
*
* The [offset] parameter shows the shift of expanded buffer start relative to origin start and could be both positive and negative.
*/
public class BufferExpanded<T>(
override val origin: Buffer<T>,
public val defaultValue: T,
public val offset: Int = 0,
override val size: Int = origin.size,
) : BufferView<T> {
init {
require(size > 0) { "Size must be positive" }
}
override fun get(index: Int): T = when (index) {
!in 0 until size -> throw IndexOutOfBoundsException("Index $index is not in $indices")
in -offset until origin.size - offset -> origin[index + offset]
else -> defaultValue
}
@UnstableKMathAPI
override fun originIndex(index: Int): Int = if (index in -offset until origin.size - offset) index + offset else -1
override fun toString(): String = "$origin[$offset..${offset + size}]"
}
/**
* Zero-copy select a slice inside the original buffer
*/
public fun <T> Buffer<T>.slice(range: UIntRange): BufferView<T> = BufferSlice(
this,
range.first,
(range.last - range.first).toInt() + 1
)
/**
* Resize original buffer to a given range using given [range], filling additional segments with [defaultValue].
* Range left border could be negative to designate adding new blank segment to the beginning of the buffer
*/
public fun <T> Buffer<T>.expand(
range: IntRange,
defaultValue: T,
): BufferView<T> = if (range.first >= 0 && range.last < size) {
BufferSlice(
this,
range.first.toUInt(),
(range.last - range.first) + 1
)
} else {
BufferExpanded(
this,
defaultValue,
range.first,
(range.last - range.first) + 1
)
}
/**
* A [BufferView] that overrides indexing of the original buffer
*/
public class PermutatedBuffer<T>(
override val origin: Buffer<T>,
private val permutations: IntArray,
) : BufferView<T> {
init {
permutations.forEach { index ->
if (index !in origin.indices) {
throw IndexOutOfBoundsException("Index $index is not in ${origin.indices}")
}
}
}
override val size: Int get() = permutations.size
override fun get(index: Int): T = origin[permutations[index]]
override fun iterator(): Iterator<T> = permutations.asSequence().map { origin[it] }.iterator()
@UnstableKMathAPI
override fun originIndex(index: Int): Int = if (index in permutations.indices) permutations[index] else -1
override fun toString(): String = Buffer.toString(this)
}
/**
* Created a permuted view of given buffer using provided [indices]
*/
public fun <T> Buffer<T>.view(indices: IntArray): PermutatedBuffer<T> = PermutatedBuffer(this, indices)

View File

@ -13,7 +13,7 @@ import kotlin.jvm.JvmInline
* @property array the underlying array.
*/
@JvmInline
public value class DoubleBuffer(public val array: DoubleArray) : MutableBuffer<Double> {
public value class DoubleBuffer(public val array: DoubleArray) : PrimitiveBuffer<Double> {
override val size: Int get() = array.size
override operator fun get(index: Int): Double = array[index]

View File

@ -14,7 +14,7 @@ import kotlin.jvm.JvmInline
* @author Iaroslav Postovalov
*/
@JvmInline
public value class FloatBuffer(public val array: FloatArray) : MutableBuffer<Float> {
public value class FloatBuffer(public val array: FloatArray) : PrimitiveBuffer<Float> {
override val size: Int get() = array.size
override operator fun get(index: Int): Float = array[index]

View File

@ -13,7 +13,7 @@ import kotlin.jvm.JvmInline
* @property array the underlying array.
*/
@JvmInline
public value class IntBuffer(public val array: IntArray) : MutableBuffer<Int> {
public value class IntBuffer(public val array: IntArray) : PrimitiveBuffer<Int> {
override val size: Int get() = array.size
override operator fun get(index: Int): Int = array[index]

View File

@ -13,7 +13,7 @@ import kotlin.jvm.JvmInline
* @property array the underlying array.
*/
@JvmInline
public value class LongBuffer(public val array: LongArray) : MutableBuffer<Long> {
public value class LongBuffer(public val array: LongArray) : PrimitiveBuffer<Long> {
override val size: Int get() = array.size
override operator fun get(index: Int): Long = array[index]

View File

@ -95,3 +95,6 @@ public interface MutableBuffer<T> : Buffer<T> {
auto(T::class, size, initializer)
}
}
public sealed interface PrimitiveBuffer<T>: MutableBuffer<T>

View File

@ -3,10 +3,9 @@
* 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.operations
package space.kscience.kmath.structures
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.structures.*
/**
* Typealias for buffer transformations.
@ -104,3 +103,39 @@ public inline fun <T1, T2, reified R : Any> Buffer<T1>.zip(
require(size == other.size) { "Buffer size mismatch in zip: expected $size but found ${other.size}" }
return bufferFactory(size) { transform(get(it), other[it]) }
}
/**
* Simular to https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/binary-search.html.
* The implementation is copied from Kotlin stdlib
*/
public fun <T : Comparable<T>> Buffer<T>.binarySearch(element: T, fromIndex: Int = 0, toIndex: Int = size): Int {
var low = fromIndex
var high = toIndex - 1
while (low <= high) {
val mid = (low + high).ushr(1) // safe from overflows
val midVal = get(mid)
val cmp = compareValues(midVal, element)
when {
cmp < 0 -> low = mid + 1
cmp > 0 -> high = mid - 1
else -> return mid // key found
}
}
return -(low + 1) // key not found
}
/**
* Create a buffer containing sorted elements of this buffer.
*/
@Suppress("CAST_NEVER_SUCCEEDS")
public fun <T : Comparable<T>> Buffer<T>.sorted(): Buffer<T> = when (this) {
is PrimitiveBuffer -> when (this) {
is LongBuffer -> toLongArray().apply { sort() } as Buffer<T>
is FloatBuffer -> toFloatArray().apply { sort() } as Buffer<T>
is IntBuffer -> toIntArray().apply { sort() } as Buffer<T>
is DoubleBuffer -> toDoubleArray().apply { sort() } as Buffer<T>
}
else -> asIterable().sorted().asBuffer()
}

View File

@ -0,0 +1,27 @@
package space.kscience.kmath.structures
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
internal class BufferExpandedTest {
private val buffer = (0..100).toList().asBuffer()
@Test
fun shrink(){
val view = buffer.slice(20U..30U)
assertEquals(20, view[0])
assertEquals(30, view[10])
assertFails { view[11] }
}
@Test
fun expandNegative(){
val view: BufferView<Int> = buffer.expand(-20..113,0)
assertEquals(0,view[4])
assertEquals(0,view[123])
assertEquals(100, view[120])
assertFails { view[-2] }
assertFails { view[134] }
}
}

View File

@ -7,7 +7,7 @@ package space.kscience.kmath.streaming
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking
import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.structures.asSequence
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@ -13,9 +13,9 @@ import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.algebra
import space.kscience.kmath.operations.asIterable
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asIterable
import kotlin.math.pow
/*

View File

@ -11,6 +11,7 @@ import space.kscience.kmath.operations.DoubleL2Norm
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.MutableBuffer.Companion.double
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.indices
import kotlin.math.pow
public typealias DoubleVector = Point<Double>

View File

@ -8,6 +8,7 @@ import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.Field
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.indices
/**
* A simple one-pass integrator based on Gauss rule

View File

@ -5,10 +5,10 @@
package space.kscience.kmath.integration
import space.kscience.kmath.operations.map
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.map
import kotlin.jvm.Synchronized
import kotlin.math.ulp
import kotlin.native.concurrent.ThreadLocal

View File

@ -12,10 +12,14 @@ import space.kscience.kmath.interpolation.SplineInterpolator
import space.kscience.kmath.interpolation.interpolatePolynomials
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.*
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.Field
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.operations.sum
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.MutableBufferFactory
import space.kscience.kmath.structures.map
/**
* Compute analytical indefinite integral of this [PiecewisePolynomial], keeping all intervals intact

View File

@ -1,6 +1,7 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.operations.toList
import space.kscience.kmath.structures.toList
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@ -1,6 +1,6 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.operations.toList
import space.kscience.kmath.structures.toList
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@ -7,8 +7,8 @@ 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
import space.kscience.kmath.structures.asSequence
@UnstableKMathAPI

View File

@ -21,9 +21,9 @@ import space.kscience.kmath.expressions.MST
import space.kscience.kmath.expressions.MstRing
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.Structure2D
import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asSequence
/**
* A function for conversion of number to MST for pretty print

View File

@ -6,8 +6,8 @@
package space.kscience.kmath.distributions
import space.kscience.kmath.chains.Chain
import space.kscience.kmath.samplers.Sampler
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
/**
* A distribution of typed objects.

View File

@ -6,8 +6,8 @@
package space.kscience.kmath.distributions
import space.kscience.kmath.chains.Chain
import space.kscience.kmath.internal.InternalErf
import space.kscience.kmath.samplers.GaussianSampler
import space.kscience.kmath.samplers.InternalErf
import space.kscience.kmath.samplers.NormalizedGaussianSampler
import space.kscience.kmath.samplers.ZigguratNormalizedGaussianSampler
import space.kscience.kmath.stat.RandomGenerator

View File

@ -3,12 +3,11 @@
* 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.stat
package space.kscience.kmath.distributions
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.stat.RandomGenerator
public class UniformDistribution(public val range: ClosedFloatingPointRange<Double>) : UnivariateDistribution<Double> {
private val length: Double = range.endInclusive - range.start

View File

@ -7,7 +7,6 @@ package space.kscience.kmath.samplers
import space.kscience.kmath.chains.BlockingDoubleChain
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
import space.kscience.kmath.structures.DoubleBuffer
import kotlin.math.ln
import kotlin.math.pow
@ -67,7 +66,7 @@ public class AhrensDieterExponentialSampler(public val mean: Double) : Sampler<D
var qi = 0.0
DoubleArray(16) { i ->
qi += ln2.pow(i + 1.0) / space.kscience.kmath.internal.InternalUtils.factorial(i + 1)
qi += ln2.pow(i + 1.0) / InternalUtils.factorial(i + 1)
qi
}
}

View File

@ -7,9 +7,7 @@ package space.kscience.kmath.samplers
import space.kscience.kmath.chains.Chain
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
import space.kscience.kmath.stat.chain
import space.kscience.kmath.stat.next
import kotlin.math.*
/**

View File

@ -6,9 +6,7 @@
package space.kscience.kmath.samplers
import space.kscience.kmath.chains.Chain
import space.kscience.kmath.internal.InternalUtils
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
import space.kscience.kmath.stat.chain
import kotlin.math.ceil
import kotlin.math.max

View File

@ -3,7 +3,7 @@
* 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.internal
package space.kscience.kmath.samplers
import kotlin.math.abs

View File

@ -3,7 +3,7 @@
* 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.internal
package space.kscience.kmath.samplers
import kotlin.math.*

View File

@ -3,7 +3,7 @@
* 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.internal
package space.kscience.kmath.samplers
import kotlin.math.ln
import kotlin.math.min

View File

@ -7,7 +7,6 @@ package space.kscience.kmath.samplers
import space.kscience.kmath.chains.BlockingIntChain
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
import space.kscience.kmath.structures.IntBuffer
import kotlin.math.exp

View File

@ -7,7 +7,6 @@ package space.kscience.kmath.samplers
import space.kscience.kmath.chains.BlockingDoubleChain
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
public interface BlockingDoubleSampler: Sampler<Double>{
override fun sample(generator: RandomGenerator): BlockingDoubleChain

View File

@ -6,10 +6,8 @@
package space.kscience.kmath.samplers
import space.kscience.kmath.chains.BlockingIntChain
import space.kscience.kmath.internal.InternalUtils
import space.kscience.kmath.misc.toIntExact
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.Sampler
import space.kscience.kmath.structures.IntBuffer
import kotlin.math.*

View File

@ -3,12 +3,16 @@
* 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.stat
package space.kscience.kmath.samplers
import kotlinx.coroutines.flow.first
import space.kscience.kmath.chains.Chain
import space.kscience.kmath.chains.collect
import space.kscience.kmath.structures.*
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.IntBuffer
import kotlin.jvm.JvmName
/**

View File

@ -3,7 +3,7 @@
* 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.stat
package space.kscience.kmath.samplers
import space.kscience.kmath.chains.Chain
import space.kscience.kmath.chains.ConstantChain
@ -12,6 +12,7 @@ import space.kscience.kmath.chains.zip
import space.kscience.kmath.operations.Group
import space.kscience.kmath.operations.ScaleOperations
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.stat.RandomGenerator
/**
* Implements [Sampler] by sampling only certain [value].

View File

@ -1,80 +1,125 @@
package space.kscience.kmath.series
import space.kscience.kmath.operations.*
import space.kscience.kmath.operations.BufferAlgebra
import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.RingOps
import space.kscience.kmath.stat.StatisticalAlgebra
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.getOrNull
import space.kscience.kmath.structures.BufferView
import kotlin.math.max
import kotlin.math.min
private fun IntRange.intersect(other: IntRange): IntRange =
@PublishedApi
internal fun IntRange.intersect(other: IntRange): IntRange =
max(first, other.first)..min(last, other.last)
private val IntRange.size get() = last - first + 1
@PublishedApi
internal val IntRange.size
get() = last - first + 1
@PublishedApi
internal operator fun IntRange.contains(other: IntRange): Boolean = (other.first in this) && (other.last in this)
//TODO add permutated buffer
//TODO add rank function
public interface Series<T> : Buffer<T> {
public val origin: Buffer<T>
/**
* Absolute position of start of this [Series] in [SeriesAlgebra]
*/
public val position: Int
}
public val <T> Series<T>.absoluteIndices: IntRange get() = position until position + size
/**
* A [BufferView] with index offset (both positive and negative) and possible size change
*/
private class OffsetBufer<T>(
override val origin: Buffer<T>,
override val position: Int,
override val size: Int = origin.size,
) : Series<T>, Buffer<T> by origin {
private class BufferView<T>(val buffer: Buffer<T>, val offset: Int, override val size: Int) : Buffer<T> {
init {
require(offset >= 0) { " Range offset must be positive" }
require(offset < buffer.size) { "Range offset is beyond the buffer size" }
require(size > 0) { "Size must be positive" }
require(size < buffer.size) { "Slice size is larger than the buffer" }
require(size <= origin.size) { "Slice size is larger than the original buffer" }
}
override fun get(index: Int): T = buffer[index - offset]
override fun iterator(): Iterator<T> = buffer.asSequence().drop(offset).take(size).iterator()
override fun toString(): String = "$buffer[${offset}:${offset + size - 1}]"
override val indices: IntRange = offset until offset + size
override fun toString(): String = "$origin-->${position}"
}
/**
* A scope to operation on series
*/
public class SeriesAlgebra<T, A : Ring<T>, L>(
public val bufferAlgebra: BufferRingOps<T, A>,
public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
override val bufferAlgebra: BA,
private val labelResolver: (Int) -> L,
) : RingOps<Buffer<T>> {
) : RingOps<Buffer<T>>, StatisticalAlgebra<T, A, BA> {
public val elementAlgebra: A get() = bufferAlgebra.elementAlgebra
public val bufferFactory: BufferFactory<T> get() = bufferAlgebra.bufferFactory
public val Buffer<T>.indices: IntRange
get() = if (this is Series) {
absoluteIndices
} else {
0 until size
}
public val Buffer<T>.offset: UInt get() = indices.first.toUInt()
/**
* Get the value by absolute index in the series algebra or return null if index is out of range
*/
public fun Buffer<T>.getAbsoluteOrNull(index: Int): T? = when {
index !in indices -> null
this is Series -> origin[index - position]
else -> get(index)
}
/**
* Get the value by absolute index in the series algebra or throw [IndexOutOfBoundsException] if index is out of range
*/
public fun Buffer<T>.getAbsolute(index: Int): T =
getAbsoluteOrNull(index) ?: throw IndexOutOfBoundsException("Index $index is not in $indices")
/**
* Create an offset series with index starting point at [index]
*/
public fun Buffer<T>.moveTo(index: Int): Series<T> = if (this is Series) {
OffsetBufer(origin, index, size)
} else {
OffsetBufer(this, index, size)
}
/**
* Create a buffer view using given absolute range
*/
public fun Buffer<T>.slice(range: IntRange): Series<T> {
val size = range.size
return if (this is Series) {
OffsetBufer(this, indices.first + range.first, size)
} else {
OffsetBufer(this, range.first, size)
}
}
public fun Buffer<T>.expand(range: IntRange, defaultValue: T): Series<T> = if (range in indices) {
slice(range)
} else {
TODO()
}
public val Buffer<T>.offset: Int get() = if (this is Series) position else 0
/**
* Build a new series
*/
public fun series(size: Int, fromIndex: Int = 0, block: A.(label: L) -> T): Buffer<T> {
public fun series(size: Int, fromIndex: Int = 0, block: A.(label: L) -> T): Series<T> {
return bufferFactory(size) {
val index = it + fromIndex
elementAlgebra.block(labelResolver(index))
}.moveTo(fromIndex)
}
/**
* Move a series starting to start at a given index
*/
public fun Buffer<T>.moveTo(index: Int): Buffer<T> = if (index == 0) {
this
} else if (this is BufferView) {
BufferView(buffer, index.toInt(), size)
} else {
BufferView(this, index.toInt(), size)
}
/**
* Create a buffer view using given range
*/
public fun Buffer<T>.get(range: IntRange): Buffer<T> {
val size = range.size
return if (this is BufferView) {
BufferView(this, indices.first + range.first, size)
} else {
BufferView(this, range.first, size)
}
}
/**
* Get a label buffer for given buffer.
*/
@ -87,34 +132,15 @@ public class SeriesAlgebra<T, A : Ring<T>, L>(
public operator fun Buffer<T>.get(label: L): T? {
val index = labels.indexOf(label)
if (index == -1) return null
return get(index + offset.toInt())
}
override fun add(left: Buffer<T>, right: Buffer<T>): Buffer<T> = elementAlgebra.invoke {
val newRange = left.indices.intersect(right.indices)
//TODO optimize copy at BufferAlgebra level
bufferFactory(newRange.size) {
val offset = it + newRange.first
left[offset] + right[offset]
}.moveTo(newRange.first)
}
override fun Buffer<T>.unaryMinus(): Buffer<T> = map { -it }
override fun multiply(left: Buffer<T>, right: Buffer<T>): Buffer<T> = elementAlgebra.invoke {
val newRange = left.indices.intersect(right.indices)
bufferFactory(newRange.size) {
val offset = it + newRange.first
left[offset] * right[offset]
}
return getAbsolute(index + offset.toInt())
}
/**
* Map a series to another series of the same size
*/
public inline fun Buffer<T>.map(crossinline transform: A.(T) -> T): Buffer<T> {
public inline fun Buffer<T>.map(crossinline transform: A.(T) -> T): Series<T> {
val buf = bufferFactory(size) {
elementAlgebra.transform(get(it))
elementAlgebra.transform(getAbsolute(it))
}
return buf.moveTo(indices.first)
}
@ -122,49 +148,63 @@ public class SeriesAlgebra<T, A : Ring<T>, L>(
/**
* Map series to another series of the same size with label
*/
public inline fun Buffer<T>.mapWithLabel(crossinline transform: A.(arg: T, label: L) -> T): Buffer<T> {
public inline fun Buffer<T>.mapWithLabel(crossinline transform: A.(arg: T, label: L) -> T): Series<T> {
val labels = labels
val buf = bufferFactory(size) {
elementAlgebra.transform(get(it), labels[it])
elementAlgebra.transform(getAbsolute(it), labels[it])
}
return buf.moveTo(indices.first)
}
public inline fun <R> Buffer<T>.fold(initial: R, operation: A.(acc: R, T) -> R): R {
var accumulator = initial
for (index in this.indices) accumulator = elementAlgebra.operation(accumulator, get(index))
for (index in this.indices) accumulator = elementAlgebra.operation(accumulator, getAbsolute(index))
return accumulator
}
public inline fun <R> Buffer<T>.foldWithLabel(initial: R, operation: A.(acc: R, arg: T, label: L) -> R): R {
val labels = labels
var accumulator = initial
for (index in this.indices) accumulator = elementAlgebra.operation(accumulator, get(index), labels[index])
for (index in this.indices) accumulator =
elementAlgebra.operation(accumulator, getAbsolute(index), labels[index])
return accumulator
}
/**
* Zip two buffers replacing missing values with [defaultValue]
* Zip two buffers in the range whe they overlap
*/
public inline fun Buffer<T>.zip(
other: Buffer<T>,
defaultValue: T,
crossinline operation: A.(left: T?, right: T?) -> T?,
): Buffer<T> {
val start = min(indices.first, other.indices.first)
val size = max(indices.last, other.indices.last) - start
return bufferFactory(size) {
crossinline operation: A.(left: T, right: T) -> T,
): Series<T> {
val newRange = indices.intersect(other.indices)
return bufferFactory(newRange.size) {
elementAlgebra.operation(
getOrNull(it) ?: defaultValue,
other.getOrNull(it) ?: defaultValue
) ?: defaultValue
}
getAbsolute(it),
other.getAbsolute(it)
)
}.moveTo(newRange.first)
}
override fun Buffer<T>.unaryMinus(): Buffer<T> = map { -it }
override fun add(left: Buffer<T>, right: Buffer<T>): Series<T> = left.zip(right) { l, r -> l + r }
override fun multiply(left: Buffer<T>, right: Buffer<T>): Buffer<T> = left.zip(right) { l, r -> l * r }
}
public fun <T, A : Ring<T>, L> BufferRingOps<T, A>.seriesAlgebra(labels: Iterable<L>): SeriesAlgebra<T, A, L> {
public fun <T, A : Ring<T>, BA : BufferAlgebra<T, A>, L> BA.seriesAlgebra(labels: Iterable<L>): SeriesAlgebra<T, A, BA, L> {
val l = labels.toList()
return SeriesAlgebra(this) {
if (it in l.indices) l[it] else error("Index $it is outside of labels range ${l.indices}")
}
}
public fun <T, A : Ring<T>, BA : BufferAlgebra<T, A>, L> BA.seriesAlgebra(labelGenerator: (Int) -> L): SeriesAlgebra<T, A, BA, L> =
SeriesAlgebra(this, labelGenerator)
/**
* Create a series algebra using offset as a label
*/
public fun <T, A : Ring<T>, BA : BufferAlgebra<T, A>> BA.seriesAlgebra(): SeriesAlgebra<T, A, BA, Int> =
SeriesAlgebra(this) { it }

View File

@ -0,0 +1,97 @@
package space.kscience.kmath.series
import space.kscience.kmath.operations.BufferAlgebra
import space.kscience.kmath.operations.ExponentialOperations
import space.kscience.kmath.operations.PowerOperations
import space.kscience.kmath.operations.TrigonometricOperations
import space.kscience.kmath.structures.Buffer
//trigonometric
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sin(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.sin(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.cos(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.cos(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.tan(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.tan(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.asin(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.asin(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.acos(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.acos(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atan(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.atan(arg).moveTo(arg.offset)
//exponential
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.exp(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.exp(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.ln(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.ln(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sinh(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.sinh(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.cosh(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.cosh(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.tanh(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.tanh(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.asinh(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.asinh(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.acosh(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.acosh(arg).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atanh(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.atanh(arg).moveTo(arg.offset)
//power
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.power(
arg: Buffer<T>,
pow: Number,
): Series<T> where BA : BufferAlgebra<T, *>, BA : PowerOperations<Buffer<T>> =
bufferAlgebra.power(arg, pow).moveTo(arg.offset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sqrt(
arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : PowerOperations<Buffer<T>> =
bufferAlgebra.sqrt(arg).moveTo(arg.offset)

View File

@ -7,6 +7,7 @@ package space.kscience.kmath.stat
import space.kscience.kmath.operations.*
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.indices
/**
* Arithmetic mean

View File

@ -5,8 +5,8 @@
package space.kscience.kmath.stat
import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asSequence
/**
* Non-composable median

View File

@ -0,0 +1,28 @@
package space.kscience.kmath.stat
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asIterable
public class Rank<T: Comparable<T>>: BlockingStatistic<T, IntArray> {
override fun evaluateBlocking(data: Buffer<T>): IntArray {
// https://www.geeksforgeeks.org/rank-elements-array/
val permutations = ArrayList<Pair<T, Int>>(data.size)
data.asIterable().mapIndexedTo(permutations) { i, v -> v to i }
permutations.sortBy { it.first }
var rank = 1
var i = 0
val r = IntArray(data.size) { 0 }
while (i < data.size) {
var j = i
while (j < data.size - 1 && permutations[j].first == permutations[j + 1]) ++j
val n = j - i + 1
(0 until n).map { k ->
val idx = permutations[i + k].second
r[idx] = rank + ((n - 1) * 0.5f).toInt()
}
rank += n
i += n
}
return r
}
}

View File

@ -0,0 +1,59 @@
package space.kscience.kmath.stat
import space.kscience.kmath.operations.*
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.asIterable
import space.kscience.kmath.structures.sorted
public interface StatisticalAlgebra<T, out A : Algebra<T>, out BA : BufferAlgebra<T, A>> : Algebra<Buffer<T>> {
public val bufferAlgebra: BA
public val elementAlgebra: A get() = bufferAlgebra.elementAlgebra
public val bufferFactory: BufferFactory<T> get() = bufferAlgebra.bufferFactory
}
/**
* Compute [empirical CDF function](https://en.wikipedia.org/wiki/Empirical_distribution_function)
*/
public fun <T : Comparable<T>> StatisticalAlgebra<T, *, *>.ecdf(buffer: Buffer<T>): (T) -> Double = { arg ->
buffer.asIterable().count { it < arg }.toDouble() / buffer.size
}
/**
* Implementation copied from https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/index.html?org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.html
*/
public fun <T : Comparable<T>, A, BA : BufferAlgebra<T, A>> StatisticalAlgebra<T, A, BA>.kolmogorovSmirnovTest(
x: Buffer<T>,
y: Buffer<T>,
): T where A : Group<T>, A : NumericAlgebra<T> = elementAlgebra.invoke {
// Copy and sort the sample arrays
val sx = x.sorted()
val sy = y.sorted()
val n = sx.size
val m = sy.size
var rankX = 0
var rankY = 0
var curD: T = zero
// Find the max difference between cdf_x and cdf_y
var supD: T = zero
do {
val z = if (sx[rankX] <= sy[rankY]) sx[rankX] else sy[rankY]
while (rankX < n && sx[rankX].compareTo(z) == 0) {
rankX += 1;
curD += number(m);
}
while (rankY < m && sy[rankY].compareTo(z) == 0) {
rankY += 1;
curD -= number(n);
}
when {
curD > supD -> supD = curD
-curD > supD -> supD = -curD
}
} while (rankX < n && rankY < m);
return supD;
}

View File

@ -6,6 +6,8 @@
package space.kscience.kmath.stat
import kotlinx.coroutines.runBlocking
import space.kscience.kmath.samplers.Sampler
import space.kscience.kmath.samplers.sampleBuffer
import kotlin.test.Test
class SamplerTest {

View File

@ -15,6 +15,7 @@ import space.kscience.kmath.nd.as1D
import space.kscience.kmath.nd.as2D
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.structures.indices
import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra
import space.kscience.kmath.tensors.api.LinearOpsTensorAlgebra
import space.kscience.kmath.tensors.api.Tensor

View File

@ -12,9 +12,9 @@ import space.kscience.kmath.nd.MutableStructure1D
import space.kscience.kmath.nd.MutableStructure2D
import space.kscience.kmath.nd.as1D
import space.kscience.kmath.nd.as2D
import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.VirtualBuffer
import space.kscience.kmath.structures.asSequence
import space.kscience.kmath.tensors.core.BufferedTensor
import space.kscience.kmath.tensors.core.DoubleTensor
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra

View File

@ -6,7 +6,6 @@
package space.kscience.kmath.tensors.core.internal
import space.kscience.kmath.nd.as1D
import space.kscience.kmath.operations.toMutableList
import space.kscience.kmath.samplers.GaussianSampler
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.structures.*