Cleanup after nd optimization phase II

This commit is contained in:
Alexander Nozik 2019-01-08 19:10:08 +03:00
parent 05d15d5b34
commit 876a363e0b
16 changed files with 433 additions and 359 deletions

View File

@ -17,7 +17,7 @@ open class NDFieldBenchmark {
@Benchmark @Benchmark
fun autoElementAdd() { fun autoElementAdd() {
var res = bufferedField.run { one.toElement() } var res = genericField.one
repeat(n) { repeat(n) {
res += 1.0 res += 1.0
} }

View File

@ -16,54 +16,54 @@ fun main(args: Array<String>) {
//A generic boxing field. It should be used for objects, not primitives. //A generic boxing field. It should be used for objects, not primitives.
val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) val genericField = NDField.buffered(intArrayOf(dim, dim), RealField)
//
val autoTime = measureTimeMillis { // val autoTime = measureTimeMillis {
autoField.run { // autoField.run {
var res = one // var res = one
repeat(n) { // repeat(n) {
res += 1.0 // res += 1.0
} // }
} // }
} // }
//
println("Buffered addition completed in $autoTime millis") // println("Buffered addition completed in $autoTime millis")
//
val elementTime = measureTimeMillis { // val elementTime = measureTimeMillis {
var res = genericField.one // var res = genericField.one
repeat(n) { // repeat(n) {
res += 1.0 // res += 1.0
} // }
} // }
//
println("Element addition completed in $elementTime millis") // println("Element addition completed in $elementTime millis")
//
val specializedTime = measureTimeMillis { // val specializedTime = measureTimeMillis {
specializedField.run { // specializedField.run {
var res:NDBuffer<Double> = one // var res:NDBuffer<Double> = one
repeat(n) { // repeat(n) {
res += 1.0 // res += 1.0
} // }
} // }
} // }
//
println("Specialized addition completed in $specializedTime millis") // println("Specialized addition completed in $specializedTime millis")
//
//
val lazyTime = measureTimeMillis { // val lazyTime = measureTimeMillis {
lazyField.run { // lazyField.run {
val res = one.map { // val res = one.map {
var c = 0.0 // var c = 0.0
repeat(n) { // repeat(n) {
c += 1.0 // c += 1.0
} // }
c // c
} // }
//
res.elements().forEach { it.second } // res.elements().forEach { it.second }
} // }
} // }
//
println("Lazy addition completed in $lazyTime millis") // println("Lazy addition completed in $lazyTime millis")
val genericTime = measureTimeMillis { val genericTime = measureTimeMillis {
//genericField.run(action) //genericField.run(action)

View File

@ -33,9 +33,9 @@ interface Space<T> {
operator fun T.times(k: Number) = multiply(this, k.toDouble()) operator fun T.times(k: Number) = multiply(this, k.toDouble())
operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble())
operator fun Number.times(b: T) = b * this operator fun Number.times(b: T) = b * this
fun Iterable<T>.sum(): T = fold(zero) { left, right -> left + right } fun Iterable<T>.sum(): T = fold(zero) { left, right -> add(left,right) }
fun Sequence<T>.sum(): T = fold(zero) { left, right -> left + right } fun Sequence<T>.sum(): T = fold(zero) { left, right -> add(left, right) }
} }
/** /**

View File

@ -2,13 +2,20 @@ package scientifik.kmath.operations
/** /**
* The generic mathematics elements which is able to store its context * The generic mathematics elements which is able to store its context
* @param S the type of mathematical context for this element * @param T the type of space operation results
* @param I self type of the element. Needed for static type checking
* @param C the type of mathematical context for this element
*/ */
interface MathElement<S> { interface MathElement<C> {
/** /**
* The context this element belongs to * The context this element belongs to
*/ */
val context: S val context: C
}
interface MathWrapper<T, I> {
fun unwrap(): T
fun T.wrap(): I
} }
/** /**
@ -17,13 +24,7 @@ interface MathElement<S> {
* @param I self type of the element. Needed for static type checking * @param I self type of the element. Needed for static type checking
* @param S the type of space * @param S the type of space
*/ */
interface SpaceElement<T, I : SpaceElement<T, I, S>, S : Space<T>> : MathElement<S> { interface SpaceElement<T, I : SpaceElement<T, I, S>, S : Space<T>> : MathElement<S>, MathWrapper<T, I> {
/**
* Self value. Needed for static type checking.
*/
fun unwrap(): T
fun T.wrap(): I
operator fun plus(b: T) = context.add(unwrap(), b).wrap() operator fun plus(b: T) = context.add(unwrap(), b).wrap()
operator fun minus(b: T) = context.add(unwrap(), context.multiply(b, -1.0)).wrap() operator fun minus(b: T) = context.add(unwrap(), context.multiply(b, -1.0)).wrap()
@ -35,7 +36,6 @@ interface SpaceElement<T, I : SpaceElement<T, I, S>, S : Space<T>> : MathElement
* Ring element * Ring element
*/ */
interface RingElement<T, I : RingElement<T, I, R>, R : Ring<T>> : SpaceElement<T, I, R> { interface RingElement<T, I : RingElement<T, I, R>, R : Ring<T>> : SpaceElement<T, I, R> {
override val context: R
operator fun times(b: T) = context.multiply(unwrap(), b).wrap() operator fun times(b: T) = context.multiply(unwrap(), b).wrap()
} }
@ -44,6 +44,5 @@ interface RingElement<T, I : RingElement<T, I, R>, R : Ring<T>> : SpaceElement<T
*/ */
interface FieldElement<T, I : FieldElement<T, I, F>, F : Field<T>> : RingElement<T, I, F> { interface FieldElement<T, I : FieldElement<T, I, F>, F : Field<T>> : RingElement<T, I, F> {
override val context: F override val context: F
operator fun div(b: T) = context.divide(unwrap(), b).wrap() operator fun div(b: T) = context.divide(unwrap(), b).wrap()
} }

View File

@ -32,6 +32,7 @@ inline class Real(val value: Double) : FieldElement<Double, Real, RealField> {
/** /**
* A field for double without boxing. Does not produce appropriate field element * A field for double without boxing. Does not produce appropriate field element
*/ */
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
object RealField : ExtendedField<Double>, Norm<Double, Double> { object RealField : ExtendedField<Double>, Norm<Double, Double> {
override val zero: Double = 0.0 override val zero: Double = 0.0
override fun add(a: Double, b: Double): Double = a + b override fun add(a: Double, b: Double): Double = a + b
@ -45,10 +46,13 @@ object RealField : ExtendedField<Double>, Norm<Double, Double> {
override fun power(arg: Double, pow: Double): Double = arg.pow(pow) override fun power(arg: Double, pow: Double): Double = arg.pow(pow)
override fun exp(arg: Double): Double = kotlin.math.exp(arg) override fun exp(arg: Double): Double = kotlin.math.exp(arg)
override fun ln(arg: Double): Double = kotlin.math.ln(arg) override fun ln(arg: Double): Double = kotlin.math.ln(arg)
override fun norm(arg: Double): Double = kotlin.math.abs(arg) override fun norm(arg: Double): Double = kotlin.math.abs(arg)
override fun Double.unaryMinus(): Double = -this
override fun Double.minus(b: Double): Double = this - b
} }
/** /**

View File

@ -1,15 +1,19 @@
package scientifik.kmath.structures package scientifik.kmath.structures
import scientifik.kmath.operations.Field import scientifik.kmath.operations.Field
import scientifik.kmath.operations.FieldElement
class BufferNDField<T, F : Field<T>>( class BoxingNDField<T, F : Field<T>>(
shape: IntArray, override val shape: IntArray,
override val elementContext: F, override val elementContext: F,
val bufferFactory: BufferFactory<T> val bufferFactory: BufferFactory<T>
) : StridedNDField<T, F>(shape), NDField<T, F, NDBuffer<T>> { ) : BufferedNDField<T, F> {
override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T> = bufferFactory(size, initializer) override val strides: Strides = DefaultStrides(shape)
override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T> =
bufferFactory(size, initializer)
override fun check(vararg elements: NDBuffer<T>) { override fun check(vararg elements: NDBuffer<T>) {
if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides")
@ -18,14 +22,14 @@ class BufferNDField<T, F : Field<T>>(
override val zero by lazy { produce { zero } } override val zero by lazy { produce { zero } }
override val one by lazy { produce { one } } override val one by lazy { produce { one } }
override fun produce(initializer: F.(IntArray) -> T): StridedNDFieldElement<T, F> = override fun produce(initializer: F.(IntArray) -> T) =
StridedNDFieldElement( BufferedNDFieldElement(
this, this,
buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) })
override fun map(arg: NDBuffer<T>, transform: F.(T) -> T): StridedNDFieldElement<T, F> { override fun map(arg: NDBuffer<T>, transform: F.(T) -> T): BufferedNDFieldElement<T, F> {
check(arg) check(arg)
return StridedNDFieldElement( return BufferedNDFieldElement(
this, this,
buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) })
} }
@ -33,9 +37,9 @@ class BufferNDField<T, F : Field<T>>(
override fun mapIndexed( override fun mapIndexed(
arg: NDBuffer<T>, arg: NDBuffer<T>,
transform: F.(index: IntArray, T) -> T transform: F.(index: IntArray, T) -> T
): StridedNDFieldElement<T, F> { ): BufferedNDFieldElement<T, F> {
check(arg) check(arg)
return StridedNDFieldElement( return BufferedNDFieldElement(
this, this,
buildBuffer(arg.strides.linearSize) { offset -> buildBuffer(arg.strides.linearSize) { offset ->
elementContext.transform( elementContext.transform(
@ -49,10 +53,13 @@ class BufferNDField<T, F : Field<T>>(
a: NDBuffer<T>, a: NDBuffer<T>,
b: NDBuffer<T>, b: NDBuffer<T>,
transform: F.(T, T) -> T transform: F.(T, T) -> T
): StridedNDFieldElement<T, F> { ): BufferedNDFieldElement<T, F> {
check(a, b) check(a, b)
return StridedNDFieldElement( return BufferedNDFieldElement(
this, this,
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
} }
override fun NDBuffer<T>.toElement(): FieldElement<NDBuffer<T>, *, out BufferedNDField<T, F>> =
BufferedNDFieldElement(this@BoxingNDField, buffer)
} }

View File

@ -0,0 +1,45 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.*
interface BufferedNDAlgebra<T, C>: NDAlgebra<T, C, NDBuffer<T>>{
val strides: Strides
fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T>
override fun check(vararg elements: NDBuffer<T>) {
if (!elements.all { it.strides == this.strides }) error("Strides mismatch")
}
/**
* Convert any [NDStructure] to buffered structure using strides from this context.
* If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes
*
* If the argument is [NDBuffer] with different strides structure, the new element will be produced.
*/
fun NDStructure<T>.toBuffer(): NDBuffer<T> {
return if (this is NDBuffer<T> && this.strides == this@BufferedNDAlgebra.strides) {
this
} else {
produce { index -> get(index) }
}
}
/**
* Convert a buffer to element of this algebra
*/
fun NDBuffer<T>.toElement(): MathElement<out BufferedNDAlgebra<T, C>>
}
interface BufferedNDSpace<T, S : Space<T>> : NDSpace<T, S, NDBuffer<T>>, BufferedNDAlgebra<T,S> {
override fun NDBuffer<T>.toElement(): SpaceElement<NDBuffer<T>, *, out BufferedNDSpace<T, S>>
}
interface BufferedNDRing<T, R : Ring<T>> : NDRing<T, R, NDBuffer<T>>, BufferedNDSpace<T, R> {
override fun NDBuffer<T>.toElement(): RingElement<NDBuffer<T>, *, out BufferedNDRing<T, R>>
}
interface BufferedNDField<T, F : Field<T>> : NDField<T, F, NDBuffer<T>>, BufferedNDRing<T, F> {
override fun NDBuffer<T>.toElement(): FieldElement<NDBuffer<T>, *, out BufferedNDField<T, F>>
}

View File

@ -0,0 +1,88 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.*
/**
* Base interface for an element with context, containing strides
*/
interface BufferedNDElement<T, C> : NDBuffer<T>, NDElement<T, C, NDBuffer<T>> {
override val context: BufferedNDAlgebra<T, C>
override val strides get() = context.strides
override val shape: IntArray get() = context.shape
}
class BufferedNDSpaceElement<T, S : Space<T>>(
override val context: BufferedNDSpace<T, S>,
override val buffer: Buffer<T>
) : BufferedNDElement<T, S>, SpaceElement<NDBuffer<T>, BufferedNDSpaceElement<T, S>, BufferedNDSpace<T, S>> {
override fun unwrap(): NDBuffer<T> = this
override fun NDBuffer<T>.wrap(): BufferedNDSpaceElement<T, S> {
context.check(this)
return BufferedNDSpaceElement(context, buffer)
}
}
class BufferedNDRingElement<T, R : Ring<T>>(
override val context: BufferedNDRing<T, R>,
override val buffer: Buffer<T>
) : BufferedNDElement<T, R>, RingElement<NDBuffer<T>, BufferedNDRingElement<T, R>, BufferedNDRing<T, R>> {
override fun unwrap(): NDBuffer<T> = this
override fun NDBuffer<T>.wrap(): BufferedNDRingElement<T, R> {
context.check(this)
return BufferedNDRingElement(context, buffer)
}
}
class BufferedNDFieldElement<T, F : Field<T>>(
override val context: BufferedNDField<T, F>,
override val buffer: Buffer<T>
) : BufferedNDElement<T, F>, FieldElement<NDBuffer<T>, BufferedNDFieldElement<T, F>, BufferedNDField<T, F>> {
override fun unwrap(): NDBuffer<T> = this
override fun NDBuffer<T>.wrap(): BufferedNDFieldElement<T, F> {
context.check(this)
return BufferedNDFieldElement(context, buffer)
}
}
/**
* Element by element application of any operation on elements to the whole array. Just like in numpy
*/
operator fun <T : Any, F : Field<T>> Function1<T, T>.invoke(ndElement: BufferedNDElement<T, F>) =
ndElement.context.run { map(ndElement) { invoke(it) }.toElement() }
/* plus and minus */
/**
* Summation operation for [BufferedNDElement] and single element
*/
operator fun <T : Any, F : Space<T>> BufferedNDElement<T, F>.plus(arg: T) =
context.map(this) { it + arg }.wrap()
/**
* Subtraction operation between [BufferedNDElement] and single element
*/
operator fun <T : Any, F : Space<T>> BufferedNDElement<T, F>.minus(arg: T) =
context.map(this) { it - arg }.wrap()
/* prod and div */
/**
* Product operation for [BufferedNDElement] and single element
*/
operator fun <T : Any, F : Ring<T>> BufferedNDElement<T, F>.times(arg: T) =
context.map(this) { it * arg }.wrap()
/**
* Division operation between [BufferedNDElement] and single element
*/
operator fun <T : Any, F : Field<T>> BufferedNDElement<T, F>.div(arg: T) =
context.map(this) { it / arg }.wrap()

View File

@ -30,7 +30,7 @@ interface Buffer<T> {
* Create most appropriate immutable buffer for given type avoiding boxing wherever possible * Create most appropriate immutable buffer for given type avoiding boxing wherever possible
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> auto(size: Int, initializer: (Int) -> T): Buffer<T> { inline fun <reified T : Any> auto(size: Int, crossinline initializer: (Int) -> T): Buffer<T> {
return when (T::class) { return when (T::class) {
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer<T> Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer<T>
Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer<T> Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer<T>
@ -40,8 +40,10 @@ interface Buffer<T> {
} }
} }
val DoubleBufferFactory: BufferFactory<Double> = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } val DoubleBufferFactory: BufferFactory<Double> =
val ShortBufferFactory: BufferFactory<Short> = { size, initializer -> ShortBuffer(ShortArray(size, initializer)) } { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) }
val ShortBufferFactory: BufferFactory<Short> =
{ size, initializer -> ShortBuffer(ShortArray(size, initializer)) }
val IntBufferFactory: BufferFactory<Int> = { size, initializer -> IntBuffer(IntArray(size, initializer)) } val IntBufferFactory: BufferFactory<Int> = { size, initializer -> IntBuffer(IntArray(size, initializer)) }
val LongBufferFactory: BufferFactory<Long> = { size, initializer -> LongBuffer(LongArray(size, initializer)) } val LongBufferFactory: BufferFactory<Long> = { size, initializer -> LongBuffer(LongArray(size, initializer)) }
} }

View File

@ -11,40 +11,79 @@ import scientifik.kmath.operations.Space
class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException()
interface NDSpace<T, S : Space<T>, N : NDStructure<T>> : Space<N> { /**
* The base interface for all nd-algebra implementations
* @param T the type of nd-structure element
* @param C the type of the context
* @param N the type of the structure
*/
interface NDAlgebra<T, C, N : NDStructure<T>> {
val shape: IntArray val shape: IntArray
val elementContext: S val elementContext: C
/** /**
* Produce a new [N] structure using given initializer function * Produce a new [N] structure using given initializer function
*/ */
fun produce(initializer: S.(IntArray) -> T): N fun produce(initializer: C.(IntArray) -> T): N
fun map(arg: N, transform: S.(T) -> T): N /**
fun mapIndexed(arg: N, transform: S.(index: IntArray, T) -> T): N * Map elements from one structure to another one
fun combine(a: N, b: N, transform: S.(T, T) -> T): N */
fun map(arg: N, transform: C.(T) -> T): N
/**
* Map indexed elements
*/
fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N
/**
* Combine two structures into one
*/
fun combine(a: N, b: N, transform: C.(T, T) -> T): N
/**
* Check if given elements are consistent with this context
*/
fun check(vararg elements: N) {
elements.forEach {
if (!shape.contentEquals(it.shape)) {
throw ShapeMismatchException(shape, it.shape)
}
}
}
/**
* element-by-element invoke a function working on [T] on a [NDStructure]
*/
operator fun Function1<T, T>.invoke(structure: N) = map(structure) { value -> this@invoke(value) }
}
/**
* An nd-space over element space
*/
interface NDSpace<T, S : Space<T>, N : NDStructure<T>> : Space<N>, NDAlgebra<T, S, N> {
/** /**
* Element-by-element addition * Element-by-element addition
*/ */
override fun add(a: N, b: N): N = override fun add(a: N, b: N): N =
combine(a, b) { aValue, bValue -> aValue + bValue } combine(a, b) { aValue, bValue -> add(aValue, bValue) }
/** /**
* Multiply all elements by constant * Multiply all elements by constant
*/ */
override fun multiply(a: N, k: Double): N = override fun multiply(a: N, k: Double): N =
map(a) { it * k } map(a) { multiply(it, k) }
operator fun Function1<T, T>.invoke(structure: N) = map(structure) { value -> this@invoke(value) } operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) }
operator fun N.plus(arg: T) = map(this) { value -> elementContext.run { arg + value } } operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) }
operator fun N.minus(arg: T) = map(this) { value -> elementContext.run { arg - value } }
operator fun T.plus(arg: N) = arg + this
operator fun T.minus(arg: N) = arg - this
operator fun T.plus(arg: N) = map(arg) { value -> add(this@plus, value) }
operator fun T.minus(arg: N) = map(arg) { value -> add(-this@minus, value) }
} }
/**
* An nd-ring over element ring
*/
interface NDRing<T, R : Ring<T>, N : NDStructure<T>> : Ring<N>, NDSpace<T, R, N> { interface NDRing<T, R : Ring<T>, N : NDStructure<T>> : Ring<N>, NDSpace<T, R, N> {
/** /**
@ -53,16 +92,16 @@ interface NDRing<T, R : Ring<T>, N : NDStructure<T>> : Ring<N>, NDSpace<T, R, N>
override fun multiply(a: N, b: N): N = override fun multiply(a: N, b: N): N =
combine(a, b) { aValue, bValue -> aValue * bValue } combine(a, b) { aValue, bValue -> aValue * bValue }
operator fun N.times(arg: T) = map(this) { value -> elementContext.run { arg * value } } operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) }
operator fun T.times(arg: N) = arg * this operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) }
} }
/** /**
* Field for n-dimensional arrays. * Field for n-dimensional structures.
* @param shape - the list of dimensions of the array * @param shape - the list of dimensions of the array
* @param elementField - operations field defined on individual array element * @param elementField - operations field defined on individual array element
* @param T - the type of the element contained in ND structure * @param T - the type of the element contained in ND structure
* @param F - field over structure elements * @param F - field of structure elements
* @param R - actual nd-element type of this field * @param R - actual nd-element type of this field
*/ */
interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F, N> { interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F, N> {
@ -73,17 +112,9 @@ interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F,
override fun divide(a: N, b: N): N = override fun divide(a: N, b: N): N =
combine(a, b) { aValue, bValue -> aValue / bValue } combine(a, b) { aValue, bValue -> aValue / bValue }
operator fun N.div(arg: T) = map(this) { value -> elementContext.run { arg / value } } operator fun N.div(arg: T) = map(this) { value -> arg / value }
operator fun T.div(arg: N) = arg / this operator fun T.div(arg: N) = arg / this
fun check(vararg elements: N) {
elements.forEach {
if (!shape.contentEquals(it.shape)) {
throw ShapeMismatchException(shape, it.shape)
}
}
}
companion object { companion object {
/** /**
* Create a nd-field for [Double] values * Create a nd-field for [Double] values
@ -94,16 +125,16 @@ interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F,
* Create a nd-field with boxing generic buffer * Create a nd-field with boxing generic buffer
*/ */
fun <T : Any, F : Field<T>> buffered(shape: IntArray, field: F) = fun <T : Any, F : Field<T>> buffered(shape: IntArray, field: F) =
BufferNDField(shape, field, Buffer.Companion::boxing) BoxingNDField(shape, field, Buffer.Companion::boxing)
/** /**
* Create a most suitable implementation for nd-field using reified class. * Create a most suitable implementation for nd-field using reified class.
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
inline fun <reified T : Any, F : Field<T>> auto(shape: IntArray, field: F): StridedNDField<T, F> = inline fun <reified T : Any, F : Field<T>> auto(shape: IntArray, field: F): BufferedNDField<T, F> =
when { when {
T::class == Double::class -> real(shape) as StridedNDField<T, F> T::class == Double::class -> real(shape) as BufferedNDField<T, F>
else -> BufferNDField(shape, field, Buffer.Companion::auto) else -> BoxingNDField(shape, field, Buffer.Companion::auto)
} }
} }
} }

View File

@ -1,16 +1,21 @@
package scientifik.kmath.structures package scientifik.kmath.structures
import scientifik.kmath.operations.Field import scientifik.kmath.operations.Field
import scientifik.kmath.operations.FieldElement
import scientifik.kmath.operations.RealField import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Ring
import scientifik.kmath.operations.Space import scientifik.kmath.operations.Space
interface NDElement<T, S : Space<T>> : NDStructure<T> { interface NDElement<T, C, N : NDStructure<T>> : NDStructure<T> {
val elementField: S
fun mapIndexed(transform: S.(index: IntArray, T) -> T): NDElement<T, S> val context: NDAlgebra<T, C, N>
fun map(action: S.(T) -> T) = mapIndexed { _, value -> action(value) }
fun unwrap(): N
fun N.wrap(): NDElement<T, C, N>
fun mapIndexed(transform: C.(index: IntArray, T) -> T) = context.mapIndexed(unwrap(), transform).wrap()
fun map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap()
companion object { companion object {
/** /**
@ -38,8 +43,8 @@ interface NDElement<T, S : Space<T>> : NDStructure<T> {
shape: IntArray, shape: IntArray,
field: F, field: F,
initializer: F.(IntArray) -> T initializer: F.(IntArray) -> T
): StridedNDFieldElement<T, F> { ): BufferedNDElement<T, F> {
val ndField = BufferNDField(shape, field, Buffer.Companion::boxing) val ndField = BoxingNDField(shape, field, Buffer.Companion::boxing)
return ndField.produce(initializer) return ndField.produce(initializer)
} }
@ -47,47 +52,46 @@ interface NDElement<T, S : Space<T>> : NDStructure<T> {
shape: IntArray, shape: IntArray,
field: F, field: F,
noinline initializer: F.(IntArray) -> T noinline initializer: F.(IntArray) -> T
): StridedNDFieldElement<T, F> { ): BufferedNDFieldElement<T, F> {
val ndField = NDField.auto(shape, field) val ndField = NDField.auto(shape, field)
return StridedNDFieldElement(ndField, ndField.produce(initializer).buffer) return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer)
} }
} }
} }
/** /**
* Element by element application of any operation on elements to the whole array. Just like in numpy * Element by element application of any operation on elements to the whole [NDElement]
*/ */
operator fun <T, F : Field<T>> Function1<T, T>.invoke(ndElement: NDElement<T, F>) = operator fun <T, C> Function1<T, T>.invoke(ndElement: NDElement<T, C, *>) =
ndElement.map { value -> this@invoke(value) } ndElement.map { value -> this@invoke(value) }
/* plus and minus */ /* plus and minus */
/** /**
* Summation operation for [NDElements] and single element * Summation operation for [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.plus(arg: T): NDElement<T, F> = operator fun <T, S : Space<T>> NDElement<T, S, *>.plus(arg: T) =
this.map { value -> elementField.run { arg + value } } map { value -> arg + value }
/** /**
* Subtraction operation between [NDElements] and single element * Subtraction operation between [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.minus(arg: T): NDElement<T, F> = operator fun <T, S : Space<T>> NDElement<T, S, *>.minus(arg: T) =
this.map { value -> elementField.run { arg - value } } map { value -> arg - value }
/* prod and div */ /* prod and div */
/** /**
* Product operation for [NDElements] and single element * Product operation for [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.times(arg: T): NDElement<T, F> = operator fun <T, R : Ring<T>> NDElement<T, R, *>.times(arg: T) =
this.map { value -> elementField.run { arg * value } } map { value -> arg * value }
/** /**
* Division operation between [NDElements] and single element * Division operation between [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> = operator fun <T, F : Field<T>> NDElement<T, F, *>.div(arg: T) =
this.map { value -> elementField.run { arg / value } } map { value -> arg / value }
// /** // /**
@ -117,24 +121,3 @@ operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> =
// operator fun T.div(arg: NDStructure<T>): NDElement<T, F> = produce { index -> // operator fun T.div(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
// field.run { this@div / arg[index] } // field.run { this@div / arg[index] }
// } // }
/**
* Read-only [NDStructure] coupled to the context.
*/
class GenericNDFieldElement<T, F : Field<T>>(
override val context: NDField<T, F, NDStructure<T>>,
private val structure: NDStructure<T>
) :
NDStructure<T> by structure,
NDElement<T, F>,
FieldElement<NDStructure<T>, GenericNDFieldElement<T, F>, NDField<T, F, NDStructure<T>>> {
override val elementField: F get() = context.elementContext
override fun unwrap(): NDStructure<T> = structure
override fun NDStructure<T>.wrap() = GenericNDFieldElement(context, this)
override fun mapIndexed(transform: F.(index: IntArray, T) -> T) =
ndStructure(context.shape) { index: IntArray -> context.elementContext.transform(index, get(index)) }.wrap()
}

View File

@ -1,13 +1,16 @@
package scientifik.kmath.structures package scientifik.kmath.structures
import scientifik.kmath.operations.FieldElement
import scientifik.kmath.operations.RealField import scientifik.kmath.operations.RealField
typealias RealNDElement = StridedNDFieldElement<Double, RealField> typealias RealNDElement = BufferedNDFieldElement<Double, RealField>
class RealNDField(shape: IntArray) : class RealNDField(override val shape: IntArray) :
StridedNDField<Double, RealField>(shape), BufferedNDField<Double, RealField>,
ExtendedNDField<Double, RealField, NDBuffer<Double>> { ExtendedNDField<Double, RealField, NDBuffer<Double>> {
override val strides: Strides = DefaultStrides(shape)
override val elementContext: RealField get() = RealField override val elementContext: RealField get() = RealField
override val zero by lazy { produce { zero } } override val zero by lazy { produce { zero } }
override val one by lazy { produce { one } } override val one by lazy { produce { one } }
@ -25,20 +28,20 @@ class RealNDField(shape: IntArray) :
): RealNDElement { ): RealNDElement {
check(arg) check(arg)
val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
return StridedNDFieldElement(this, array) return BufferedNDFieldElement(this, array)
} }
override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
return StridedNDFieldElement(this, array) return BufferedNDFieldElement(this, array)
} }
override fun mapIndexed( override fun mapIndexed(
arg: NDBuffer<Double>, arg: NDBuffer<Double>,
transform: RealField.(index: IntArray, Double) -> Double transform: RealField.(index: IntArray, Double) -> Double
): StridedNDFieldElement<Double, RealField> { ): RealNDElement {
check(arg) check(arg)
return StridedNDFieldElement( return BufferedNDFieldElement(
this, this,
buildBuffer(arg.strides.linearSize) { offset -> buildBuffer(arg.strides.linearSize) { offset ->
elementContext.transform( elementContext.transform(
@ -52,13 +55,16 @@ class RealNDField(shape: IntArray) :
a: NDBuffer<Double>, a: NDBuffer<Double>,
b: NDBuffer<Double>, b: NDBuffer<Double>,
transform: RealField.(Double, Double) -> Double transform: RealField.(Double, Double) -> Double
): StridedNDFieldElement<Double, RealField> { ): RealNDElement {
check(a, b) check(a, b)
return StridedNDFieldElement( return BufferedNDFieldElement(
this, this,
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
} }
override fun NDBuffer<Double>.toElement(): FieldElement<NDBuffer<Double>, *, out BufferedNDField<Double, RealField>> =
BufferedNDFieldElement(this@RealNDField, buffer)
override fun power(arg: NDBuffer<Double>, pow: Double) = map(arg) { power(it, pow) } override fun power(arg: NDBuffer<Double>, pow: Double) = map(arg) { power(it, pow) }
override fun exp(arg: NDBuffer<Double>) = map(arg) { exp(it) } override fun exp(arg: NDBuffer<Double>) = map(arg) { exp(it) }
@ -75,9 +81,9 @@ class RealNDField(shape: IntArray) :
/** /**
* Fast element production using function inlining * Fast element production using function inlining
*/ */
inline fun StridedNDField<Double, RealField>.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { inline fun BufferedNDField<Double, RealField>.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement {
val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) }
return StridedNDFieldElement(this, DoubleBuffer(array)) return BufferedNDFieldElement(this, DoubleBuffer(array))
} }
/** /**
@ -90,13 +96,13 @@ operator fun Function1<Double, Double>.invoke(ndElement: RealNDElement) =
/* plus and minus */ /* plus and minus */
/** /**
* Summation operation for [StridedNDFieldElement] and single element * Summation operation for [BufferedNDElement] and single element
*/ */
operator fun RealNDElement.plus(arg: Double) = operator fun RealNDElement.plus(arg: Double) =
context.produceInline { i -> buffer[i] + arg } context.produceInline { i -> buffer[i] + arg }
/** /**
* Subtraction operation between [StridedNDFieldElement] and single element * Subtraction operation between [BufferedNDElement] and single element
*/ */
operator fun RealNDElement.minus(arg: Double) = operator fun RealNDElement.minus(arg: Double) =
context.produceInline { i -> buffer[i] - arg } context.produceInline { i -> buffer[i] - arg }

View File

@ -1,86 +1,96 @@
//package scientifik.kmath.structures package scientifik.kmath.structures
//
//import scientifik.kmath.operations.ShortRing import scientifik.kmath.operations.RingElement
// import scientifik.kmath.operations.ShortRing
//
////typealias ShortNDElement = StridedNDFieldElement<Short, ShortRing>
// typealias ShortNDElement = BufferedNDRingElement<Short, ShortRing>
//class ShortNDRing(shape: IntArray) :
// NDRing<Short, ShortRing, NDBuffer<Short>> { class ShortNDRing(override val shape: IntArray) :
// BufferedNDRing<Short, ShortRing> {
// inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer<Short> =
// ShortBuffer(ShortArray(size) { initializer(it) }) override val strides: Strides = DefaultStrides(shape)
//
// /** override val elementContext: ShortRing get() = ShortRing
// * Inline transform an NDStructure to override val zero by lazy { produce { ShortRing.zero } }
// */ override val one by lazy { produce { ShortRing.one } }
// override fun map(
// arg: NDBuffer<Short>, override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer<Short> =
// transform: ShortRing.(Short) -> Short ShortBuffer(ShortArray(size) { initializer(it) })
// ): ShortNDElement {
// check(arg) /**
// val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) } * Inline transform an NDStructure to
// return StridedNDFieldElement(this, array) */
// } override fun map(
// arg: NDBuffer<Short>,
// override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement { transform: ShortRing.(Short) -> Short
// val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } ): ShortNDElement {
// return StridedNDFieldElement(this, array) check(arg)
// } val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) }
// return BufferedNDRingElement(this, array)
// override fun mapIndexed( }
// arg: NDBuffer<Short>,
// transform: ShortRing.(index: IntArray, Short) -> Short override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement {
// ): StridedNDFieldElement<Short, ShortRing> { val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
// check(arg) return BufferedNDRingElement(this, array)
// return StridedNDFieldElement( }
// this,
// buildBuffer(arg.strides.linearSize) { offset -> override fun mapIndexed(
// elementContext.transform( arg: NDBuffer<Short>,
// arg.strides.index(offset), transform: ShortRing.(index: IntArray, Short) -> Short
// arg.buffer[offset] ): ShortNDElement {
// ) check(arg)
// }) return BufferedNDRingElement(
// } this,
// buildBuffer(arg.strides.linearSize) { offset ->
// override fun combine( elementContext.transform(
// a: NDBuffer<Short>, arg.strides.index(offset),
// b: NDBuffer<Short>, arg.buffer[offset]
// transform: ShortRing.(Short, Short) -> Short )
// ): StridedNDFieldElement<Short, ShortRing> { })
// check(a, b) }
// return StridedNDFieldElement(
// this, override fun combine(
// buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) a: NDBuffer<Short>,
// } b: NDBuffer<Short>,
//} transform: ShortRing.(Short, Short) -> Short
// ): ShortNDElement {
// check(a, b)
///** return BufferedNDRingElement(
// * Fast element production using function inlining this,
// */ buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
//inline fun StridedNDField<Short, ShortRing>.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { }
// val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) }
// return StridedNDFieldElement(this, ShortBuffer(array)) override fun NDBuffer<Short>.toElement(): RingElement<NDBuffer<Short>, *, out BufferedNDRing<Short, ShortRing>> =
//} BufferedNDRingElement(this@ShortNDRing, buffer)
// }
///**
// * Element by element application of any operation on elements to the whole array. Just like in numpy
// */ /**
//operator fun Function1<Short, Short>.invoke(ndElement: ShortNDElement) = * Fast element production using function inlining
// ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } */
// inline fun BufferedNDRing<Short, ShortRing>.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement {
// val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) }
///* plus and minus */ return BufferedNDRingElement(this, ShortBuffer(array))
// }
///**
// * Summation operation for [StridedNDFieldElement] and single element /**
// */ * Element by element application of any operation on elements to the whole array. Just like in numpy
//operator fun ShortNDElement.plus(arg: Short) = */
// context.produceInline { i -> buffer[i] + arg } operator fun Function1<Short, Short>.invoke(ndElement: ShortNDElement) =
// ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) }
///**
// * Subtraction operation between [StridedNDFieldElement] and single element
// */ /* plus and minus */
//operator fun ShortNDElement.minus(arg: Short) =
// context.produceInline { i -> buffer[i] - arg } /**
* Summation operation for [StridedNDFieldElement] and single element
*/
operator fun ShortNDElement.plus(arg: Short) =
context.produceInline { i -> (buffer[i] + arg).toShort() }
/**
* Subtraction operation between [StridedNDFieldElement] and single element
*/
operator fun ShortNDElement.minus(arg: Short) =
context.produceInline { i -> (buffer[i] - arg).toShort() }

View File

@ -1,97 +0,0 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.Field
import scientifik.kmath.operations.FieldElement
abstract class StridedNDField<T, F : Field<T>>(final override val shape: IntArray) : NDField<T, F, NDBuffer<T>> {
val strides = DefaultStrides(shape)
abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T>
/**
* Convert any [NDStructure] to buffered structure using strides from this context.
* If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes
*
* If the argument is [NDBuffer] with different strides structure, the new element will be produced.
*/
fun NDStructure<T>.toBuffer(): NDBuffer<T> {
return if (this is NDBuffer<T> && this.strides == this@StridedNDField.strides) {
this
} else {
produce { index -> get(index) }
}
}
fun NDBuffer<T>.toElement(): StridedNDFieldElement<T, F> =
StridedNDFieldElement(this@StridedNDField, buffer)
}
class StridedNDFieldElement<T, F : Field<T>>(
override val context: StridedNDField<T, F>,
override val buffer: Buffer<T>
) :
NDBuffer<T>,
FieldElement<NDBuffer<T>, StridedNDFieldElement<T, F>, StridedNDField<T, F>>,
NDElement<T, F> {
override val elementField: F
get() = context.elementContext
override fun unwrap(): NDBuffer<T> =
this
override fun NDBuffer<T>.wrap(): StridedNDFieldElement<T, F> =
StridedNDFieldElement(context, this.buffer)
override val strides
get() = context.strides
override val shape: IntArray
get() = context.shape
override fun get(index: IntArray): T =
buffer[strides.offset(index)]
override fun elements(): Sequence<Pair<IntArray, T>> =
strides.indices().map { it to get(it) }
override fun map(action: F.(T) -> T) =
context.run { map(this@StridedNDFieldElement, action) }.wrap()
override fun mapIndexed(transform: F.(index: IntArray, T) -> T) =
context.run { mapIndexed(this@StridedNDFieldElement, transform) }.wrap()
}
/**
* Element by element application of any operation on elements to the whole array. Just like in numpy
*/
operator fun <T : Any, F : Field<T>> Function1<T, T>.invoke(ndElement: StridedNDFieldElement<T, F>) =
ndElement.context.run { ndElement.map { invoke(it) } }
/* plus and minus */
/**
* Summation operation for [StridedNDFieldElement] and single element
*/
operator fun <T : Any, F : Field<T>> StridedNDFieldElement<T, F>.plus(arg: T) =
context.run { map { it + arg } }
/**
* Subtraction operation between [StridedNDFieldElement] and single element
*/
operator fun <T : Any, F : Field<T>> StridedNDFieldElement<T, F>.minus(arg: T) =
context.run { map { it - arg } }
/* prod and div */
/**
* Product operation for [StridedNDFieldElement] and single element
*/
operator fun <T : Any, F : Field<T>> StridedNDFieldElement<T, F>.times(arg: T) =
context.run { map { it * arg } }
/**
* Division operation between [StridedNDFieldElement] and single element
*/
operator fun <T : Any, F : Field<T>> StridedNDFieldElement<T, F>.div(arg: T) =
context.run { map { it / arg } }

View File

@ -29,7 +29,7 @@ fun MutableBuffer.Companion.complex(size: Int) =
ObjectBuffer.create(ComplexBufferSpec, size) ObjectBuffer.create(ComplexBufferSpec, size)
fun NDField.Companion.complex(shape: IntArray) = fun NDField.Companion.complex(shape: IntArray) =
BufferNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } BoxingNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) }
fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) =
NDField.complex(shape).produce(initializer) NDField.complex(shape).produce(initializer)

View File

@ -8,8 +8,7 @@ class LazyNDField<T, F : Field<T>>(
override val shape: IntArray, override val shape: IntArray,
override val elementContext: F, override val elementContext: F,
val scope: CoroutineScope = GlobalScope val scope: CoroutineScope = GlobalScope
) : ) : NDField<T, F, NDStructure<T>> {
NDField<T, F, NDStructure<T>> {
override val zero by lazy { produce { zero } } override val zero by lazy { produce { zero } }
@ -65,7 +64,8 @@ class LazyNDField<T, F : Field<T>>(
class LazyNDStructure<T, F : Field<T>>( class LazyNDStructure<T, F : Field<T>>(
override val context: LazyNDField<T, F>, override val context: LazyNDField<T, F>,
val function: suspend (IntArray) -> T val function: suspend (IntArray) -> T
) : FieldElement<NDStructure<T>, LazyNDStructure<T, F>, LazyNDField<T, F>>, NDElement<T, F> { ) : FieldElement<NDStructure<T>, LazyNDStructure<T, F>, LazyNDField<T, F>>,
NDElement<T, F, NDStructure<T>> {
override fun unwrap(): NDStructure<T> = this override fun unwrap(): NDStructure<T> = this
@ -73,10 +73,6 @@ class LazyNDStructure<T, F : Field<T>>(
override fun NDStructure<T>.wrap(): LazyNDStructure<T, F> = LazyNDStructure(context) { await(it) } override fun NDStructure<T>.wrap(): LazyNDStructure<T, F> = LazyNDStructure(context) { await(it) }
override val shape: IntArray get() = context.shape override val shape: IntArray get() = context.shape
override val elementField: F get() = context.elementContext
override fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement<T, F> =
context.run { mapIndexed(this@LazyNDStructure, transform) }
private val cache = HashMap<IntArray, Deferred<T>>() private val cache = HashMap<IntArray, Deferred<T>>()