Matrix performance optimization

This commit is contained in:
Alexander Nozik 2021-01-24 17:07:19 +03:00
parent 3bbb39f30d
commit aff7bbab41
17 changed files with 215 additions and 129 deletions

View File

@ -31,38 +31,35 @@ class DotBenchmark {
}
@Benchmark
fun commonsMathMultiplication() {
fun cmDot() {
CMMatrixContext {
cmMatrix1 dot cmMatrix2
}
}
@Benchmark
fun ejmlMultiplication() {
fun ejmlDot() {
EjmlMatrixContext {
ejmlMatrix1 dot ejmlMatrix2
}
}
@Benchmark
fun ejmlMultiplicationwithConversion() {
fun ejmlDotWithConversion() {
EjmlMatrixContext {
val ejmlMatrix1 = matrix1.toEjml()
val ejmlMatrix2 = matrix2.toEjml()
ejmlMatrix1 dot ejmlMatrix2
matrix1 dot matrix2
}
}
@Benchmark
fun bufferedMultiplication() {
fun bufferedDot() {
BufferMatrixContext(RealField, Buffer.Companion::real).invoke {
matrix1 dot matrix2
}
}
@Benchmark
fun realMultiplication() {
fun realDot() {
RealMatrixContext {
matrix1 dot matrix2
}

View File

@ -26,7 +26,9 @@ class LinearAlgebraBenchmark {
@Benchmark
fun kmathLUPInversion() {
MatrixContext.real.inverseWithLUP(matrix)
MatrixContext.real{
inverseWithLUP(matrix)
}
}
@Benchmark

View File

@ -23,7 +23,7 @@ internal class ViktorBenchmark {
fun automaticFieldAddition() {
autoField {
var res: NDStructure<Double> = one
repeat(n) { res += one }
repeat(n) { res += 1.0 }
}
}
@ -31,7 +31,7 @@ internal class ViktorBenchmark {
fun realFieldAddition() {
realField {
var res: NDStructure<Double> = one
repeat(n) { res += one }
repeat(n) { res += 1.0 }
}
}
@ -39,7 +39,7 @@ internal class ViktorBenchmark {
fun viktorFieldAddition() {
viktorField {
var res = one
repeat(n) { res += one }
repeat(n) { res += 1.0 }
}
}

View File

@ -15,7 +15,7 @@ internal class ViktorLogBenchmark {
final val n: Int = 100
// automatically build context most suited for given type.
final val autoField: BufferedNDField<Double, RealField> = NDAlgebra.auto(RealField, dim, dim)
final val autoField: NDField<Double, RealField> = NDAlgebra.auto(RealField, dim, dim)
final val realField: RealNDField = NDAlgebra.real(dim, dim)
final val viktorField: ViktorNDField = ViktorNDField(intArrayOf(dim, dim))
@ -29,6 +29,15 @@ internal class ViktorLogBenchmark {
}
}
@Benchmark
fun viktorFieldLog() {
viktorField {
val fortyTwo = produce { 42.0 }
var res = one
repeat(n) { res = ln(fortyTwo) }
}
}
@Benchmark
fun rawViktorLog() {
val fortyTwo = F64Array.full(dim, dim, init = 42.0)

View File

@ -48,22 +48,20 @@ class StreamRealNDField(
return NDBuffer(strides, array.asBuffer())
}
override fun map(
arg: NDStructure<Double>,
override fun NDStructure<Double>.map(
transform: RealField.(Double) -> Double,
): NDBuffer<Double> {
val array = Arrays.stream(arg.buffer.array).parallel().map { RealField.transform(it) }.toArray()
val array = Arrays.stream(buffer.array).parallel().map { RealField.transform(it) }.toArray()
return NDBuffer(strides, array.asBuffer())
}
override fun mapIndexed(
arg: NDStructure<Double>,
override fun NDStructure<Double>.mapIndexed(
transform: RealField.(index: IntArray, Double) -> Double,
): NDBuffer<Double> {
val array = IntStream.range(0, strides.linearSize).parallel().mapToDouble { offset ->
RealField.transform(
strides.index(offset),
arg.buffer.array[offset]
buffer.array[offset]
)
}.toArray()
@ -81,25 +79,25 @@ class StreamRealNDField(
return NDBuffer(strides, array.asBuffer())
}
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = map(arg) { power(it, pow) }
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = arg.map() { power(it, pow) }
override fun exp(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { exp(it) }
override fun exp(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { exp(it) }
override fun ln(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { ln(it) }
override fun ln(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { ln(it) }
override fun sin(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { sin(it) }
override fun cos(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { cos(it) }
override fun tan(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { tan(it) }
override fun asin(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { asin(it) }
override fun acos(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { acos(it) }
override fun atan(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { atan(it) }
override fun sin(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { sin(it) }
override fun cos(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { cos(it) }
override fun tan(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { tan(it) }
override fun asin(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { asin(it) }
override fun acos(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { acos(it) }
override fun atan(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { atan(it) }
override fun sinh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { sinh(it) }
override fun cosh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { cosh(it) }
override fun tanh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { tanh(it) }
override fun asinh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { asinh(it) }
override fun acosh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { acosh(it) }
override fun atanh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { atanh(it) }
override fun sinh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { sinh(it) }
override fun cosh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { cosh(it) }
override fun tanh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { tanh(it) }
override fun asinh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { asinh(it) }
override fun acosh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { acosh(it) }
override fun atanh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { atanh(it) }
}
fun NDAlgebra.Companion.realWithStream(vararg shape: Int): StreamRealNDField = StreamRealNDField(shape)

View File

@ -3,6 +3,7 @@ package kscience.kmath.linear
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.Structure2D
import kscience.kmath.operations.Ring
import kscience.kmath.operations.invoke
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.asSequence
@ -28,6 +29,62 @@ public class BufferMatrixContext<T : Any, R : Ring<T>>(
public override fun point(size: Int, initializer: (Int) -> T): Point<T> = bufferFactory(size, initializer)
private fun Matrix<T>.toBufferMatrix(): BufferMatrix<T> = if (this is BufferMatrix) this else {
produce(rowNum, colNum) { i, j -> get(i, j) }
}
public fun one(rows: Int, columns: Int): Matrix<Double> = VirtualMatrix(rows, columns) { i, j ->
if (i == j) 1.0 else 0.0
} + DiagonalFeature
public override infix fun Matrix<T>.dot(other: Matrix<T>): BufferMatrix<T> {
require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" }
val bufferMatrix = toBufferMatrix()
val otherBufferMatrix = other.toBufferMatrix()
return elementContext {
produce(rowNum, other.colNum) { i, j ->
var res = one
for (l in 0 until colNum) {
res += bufferMatrix[i, l] * otherBufferMatrix[l, j]
}
res
}
}
}
public override infix fun Matrix<T>.dot(vector: Point<T>): Point<T> {
require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" }
val bufferMatrix = toBufferMatrix()
return elementContext {
bufferFactory(rowNum) { i ->
var res = one
for (j in 0 until colNum) {
res += bufferMatrix[i, j] * vector[j]
}
res
}
}
}
override fun add(a: Matrix<T>, b: Matrix<T>): BufferMatrix<T> {
require(a.rowNum == b.rowNum) { "Row number mismatch in matrix addition. Left side: ${a.rowNum}, right side: ${b.rowNum}" }
require(a.colNum == b.colNum) { "Column number mismatch in matrix addition. Left side: ${a.colNum}, right side: ${b.colNum}" }
val aBufferMatrix = a.toBufferMatrix()
val bBufferMatrix = b.toBufferMatrix()
return elementContext {
produce(a.rowNum, a.colNum) { i, j ->
aBufferMatrix[i, j] + bBufferMatrix[i, j]
}
}
}
override fun multiply(a: Matrix<T>, k: Number): BufferMatrix<T> {
val aBufferMatrix = a.toBufferMatrix()
return elementContext {
produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() }
}
}
public companion object
}

View File

@ -60,12 +60,6 @@ public operator fun <T : Any> Matrix<T>.plus(newFeatures: Collection<MatrixFeatu
MatrixWrapper(this, newFeatures.toSet())
}
public inline fun Structure2D.Companion.real(
rows: Int,
columns: Int,
initializer: (Int, Int) -> Double,
): BufferMatrix<Double> = MatrixContext.real.produce(rows, columns, initializer)
/**
* Build a square matrix from given elements.
*/

View File

@ -2,10 +2,9 @@ package kscience.kmath.linear
import kscience.kmath.structures.RealBuffer
@Suppress("OVERRIDE_BY_INLINE")
public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
public override inline fun produce(
public override fun produce(
rows: Int,
columns: Int,
initializer: (i: Int, j: Int) -> Double,
@ -14,7 +13,7 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
return BufferMatrix(rows, columns, buffer)
}
private fun Matrix<Double>.wrap(): BufferMatrix<Double> = if (this is BufferMatrix) this else {
public fun Matrix<Double>.toBufferMatrix(): BufferMatrix<Double> = if (this is BufferMatrix) this else {
produce(rowNum, colNum) { i, j -> get(i, j) }
}
@ -24,10 +23,12 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
public override infix fun Matrix<Double>.dot(other: Matrix<Double>): BufferMatrix<Double> {
require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" }
val bufferMatrix = toBufferMatrix()
val otherBufferMatrix = other.toBufferMatrix()
return produce(rowNum, other.colNum) { i, j ->
var res = 0.0
for (l in 0 until colNum) {
res += get(i, l) * other.get(l, j)
res += bufferMatrix[i, l] * otherBufferMatrix[l, j]
}
res
}
@ -35,10 +36,11 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
public override infix fun Matrix<Double>.dot(vector: Point<Double>): Point<Double> {
require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" }
val bufferMatrix = toBufferMatrix()
return RealBuffer(rowNum) { i ->
var res = 0.0
for (j in 0 until colNum) {
res += get(i, j) * vector[j]
res += bufferMatrix[i, j] * vector[j]
}
res
}
@ -47,17 +49,23 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
override fun add(a: Matrix<Double>, b: Matrix<Double>): BufferMatrix<Double> {
require(a.rowNum == b.rowNum) { "Row number mismatch in matrix addition. Left side: ${a.rowNum}, right side: ${b.rowNum}" }
require(a.colNum == b.colNum) { "Column number mismatch in matrix addition. Left side: ${a.colNum}, right side: ${b.colNum}" }
val aBufferMatrix = a.toBufferMatrix()
val bBufferMatrix = b.toBufferMatrix()
return produce(a.rowNum, a.colNum) { i, j ->
a[i, j] + b[i, j]
aBufferMatrix[i, j] + bBufferMatrix[i, j]
}
}
override fun Matrix<Double>.times(value: Double): BufferMatrix<Double> =
produce(rowNum, colNum) { i, j -> get(i, j) * value }
override fun Matrix<Double>.times(value: Double): BufferMatrix<Double> {
val bufferMatrix = toBufferMatrix()
return produce(rowNum, colNum) { i, j -> bufferMatrix[i, j] * value }
}
override fun multiply(a: Matrix<Double>, k: Number): BufferMatrix<Double> =
produce(a.rowNum, a.colNum) { i, j -> a[i, j] * k.toDouble() }
override fun multiply(a: Matrix<Double>, k: Number): BufferMatrix<Double> {
val aBufferMatrix = a.toBufferMatrix()
return produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() }
}
}

View File

@ -28,24 +28,24 @@ public interface BufferNDAlgebra<T, C> : NDAlgebra<T, C> {
else -> bufferFactory(strides.linearSize) { offset -> get(strides.index(offset)) }
}
override fun map(arg: NDStructure<T>, transform: C.(T) -> T): NDBuffer<T> {
override fun NDStructure<T>.map(transform: C.(T) -> T): NDBuffer<T> {
val buffer = bufferFactory(strides.linearSize) { offset ->
elementContext.transform(arg.buffer[offset])
elementContext.transform(buffer[offset])
}
return NDBuffer(strides, buffer)
}
override fun mapIndexed(arg: NDStructure<T>, transform: C.(index: IntArray, T) -> T): NDStructure<T> {
override fun NDStructure<T>.mapIndexed(transform: C.(index: IntArray, T) -> T): NDBuffer<T> {
val buffer = bufferFactory(strides.linearSize) { offset ->
elementContext.transform(
strides.index(offset),
arg.buffer[offset]
buffer[offset]
)
}
return NDBuffer(strides, buffer)
}
override fun combine(a: NDStructure<T>, b: NDStructure<T>, transform: C.(T, T) -> T): NDStructure<T> {
override fun combine(a: NDStructure<T>, b: NDStructure<T>, transform: C.(T, T) -> T): NDBuffer<T> {
val buffer = bufferFactory(strides.linearSize) { offset ->
elementContext.transform(a.buffer[offset], b.buffer[offset])
}

View File

@ -70,25 +70,25 @@ public class ComplexNDField(
// return BufferedNDFieldElement(this, buffer)
// }
override fun power(arg: NDStructure<Complex>, pow: Number): NDBuffer<Complex> = map(arg) { power(it, pow) }
override fun power(arg: NDStructure<Complex>, pow: Number): NDBuffer<Complex> = arg.map() { power(it, pow) }
override fun exp(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { exp(it) }
override fun exp(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { exp(it) }
override fun ln(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { ln(it) }
override fun ln(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { ln(it) }
override fun sin(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { sin(it) }
override fun cos(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { cos(it) }
override fun tan(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { tan(it) }
override fun asin(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { asin(it) }
override fun acos(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { acos(it) }
override fun atan(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { atan(it) }
override fun sin(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { sin(it) }
override fun cos(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { cos(it) }
override fun tan(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { tan(it) }
override fun asin(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { asin(it) }
override fun acos(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { acos(it) }
override fun atan(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { atan(it) }
override fun sinh(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { sinh(it) }
override fun cosh(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { cosh(it) }
override fun tanh(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { tanh(it) }
override fun asinh(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { asinh(it) }
override fun acosh(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { acosh(it) }
override fun atanh(arg: NDStructure<Complex>): NDBuffer<Complex> = map(arg) { atanh(it) }
override fun sinh(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { sinh(it) }
override fun cosh(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { cosh(it) }
override fun tanh(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { tanh(it) }
override fun asinh(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { asinh(it) }
override fun acosh(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { acosh(it) }
override fun atanh(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { atanh(it) }
}

View File

@ -40,12 +40,12 @@ public interface NDAlgebra<T, C> {
/**
* Maps elements from one structure to another one by applying [transform] to them.
*/
public fun map(arg: NDStructure<T>, transform: C.(T) -> T): NDStructure<T>
public fun NDStructure<T>.map(transform: C.(T) -> T): NDStructure<T>
/**
* Maps elements from one structure to another one by applying [transform] to them alongside with their indices.
*/
public fun mapIndexed(arg: NDStructure<T>, transform: C.(index: IntArray, T) -> T): NDStructure<T>
public fun NDStructure<T>.mapIndexed(transform: C.(index: IntArray, T) -> T): NDStructure<T>
/**
* Combines two structures into one.
@ -56,7 +56,7 @@ public interface NDAlgebra<T, C> {
* Element-wise invocation of function working on [T] on a [NDStructure].
*/
public operator fun Function1<T, T>.invoke(structure: NDStructure<T>): NDStructure<T> =
map(structure) { value -> this@invoke(value) }
structure.map() { value -> this@invoke(value) }
public companion object
}
@ -109,7 +109,7 @@ public interface NDSpace<T, S : Space<T>> : Space<NDStructure<T>>, NDAlgebra<T,
* @param k the multiplier.
* @return the product.
*/
public override fun multiply(a: NDStructure<T>, k: Number): NDStructure<T> = map(a) { multiply(it, k) }
public override fun multiply(a: NDStructure<T>, k: Number): NDStructure<T> = a.map() { multiply(it, k) }
// TODO move to extensions after KEEP-176
@ -120,7 +120,7 @@ public interface NDSpace<T, S : Space<T>> : Space<NDStructure<T>>, NDAlgebra<T,
* @param arg the augend.
* @return the sum.
*/
public operator fun NDStructure<T>.plus(arg: T): NDStructure<T> = map(this) { value -> add(arg, value) }
public operator fun NDStructure<T>.plus(arg: T): NDStructure<T> = this.map() { value -> add(arg, value) }
/**
* Subtracts an element from ND structure of it.
@ -129,7 +129,7 @@ public interface NDSpace<T, S : Space<T>> : Space<NDStructure<T>>, NDAlgebra<T,
* @param arg the divisor.
* @return the quotient.
*/
public operator fun NDStructure<T>.minus(arg: T): NDStructure<T> = map(this) { value -> add(arg, -value) }
public operator fun NDStructure<T>.minus(arg: T): NDStructure<T> = this.map() { value -> add(arg, -value) }
/**
* Adds an element to ND structure of it.
@ -138,7 +138,7 @@ public interface NDSpace<T, S : Space<T>> : Space<NDStructure<T>>, NDAlgebra<T,
* @param arg the augend.
* @return the sum.
*/
public operator fun T.plus(arg: NDStructure<T>): NDStructure<T> = map(arg) { value -> add(this@plus, value) }
public operator fun T.plus(arg: NDStructure<T>): NDStructure<T> = arg.map() { value -> add(this@plus, value) }
/**
* Subtracts an ND structure from an element of it.
@ -147,7 +147,7 @@ public interface NDSpace<T, S : Space<T>> : Space<NDStructure<T>>, NDAlgebra<T,
* @param arg the divisor.
* @return the quotient.
*/
public operator fun T.minus(arg: NDStructure<T>): NDStructure<T> = map(arg) { value -> add(-this@minus, value) }
public operator fun T.minus(arg: NDStructure<T>): NDStructure<T> = arg.map() { value -> add(-this@minus, value) }
public companion object
}
@ -179,7 +179,7 @@ public interface NDRing<T, R : Ring<T>> : Ring<NDStructure<T>>, NDSpace<T, R> {
* @param arg the multiplier.
* @return the product.
*/
public operator fun NDStructure<T>.times(arg: T): NDStructure<T> = map(this) { value -> multiply(arg, value) }
public operator fun NDStructure<T>.times(arg: T): NDStructure<T> = this.map() { value -> multiply(arg, value) }
/**
* Multiplies an element by a ND structure of it.
@ -188,7 +188,7 @@ public interface NDRing<T, R : Ring<T>> : Ring<NDStructure<T>>, NDSpace<T, R> {
* @param arg the multiplier.
* @return the product.
*/
public operator fun T.times(arg: NDStructure<T>): NDStructure<T> = map(arg) { value -> multiply(this@times, value) }
public operator fun T.times(arg: NDStructure<T>): NDStructure<T> = arg.map() { value -> multiply(this@times, value) }
public companion object
}
@ -219,7 +219,7 @@ public interface NDField<T, F : Field<T>> : Field<NDStructure<T>>, NDRing<T, F>
* @param arg the divisor.
* @return the quotient.
*/
public operator fun NDStructure<T>.div(arg: T): NDStructure<T> = map(this) { value -> divide(arg, value) }
public operator fun NDStructure<T>.div(arg: T): NDStructure<T> = this.map() { value -> divide(arg, value) }
/**
* Divides an element by an ND structure of it.
@ -228,7 +228,7 @@ public interface NDField<T, F : Field<T>> : Field<NDStructure<T>>, NDRing<T, F>
* @param arg the divisor.
* @return the quotient.
*/
public operator fun T.div(arg: NDStructure<T>): NDStructure<T> = map(arg) { divide(it, this@div) }
public operator fun T.div(arg: NDStructure<T>): NDStructure<T> = arg.map() { divide(it, this@div) }
// @ThreadLocal
// public companion object {

View File

@ -35,11 +35,10 @@ public class RealNDField(
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun map(
arg: NDStructure<Double>,
override inline fun NDStructure<Double>.map(
transform: RealField.(Double) -> Double,
): NDBuffer<Double> {
val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(arg.buffer.array[offset]) }
val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(buffer.array[offset]) }
return NDBuffer(strides, buffer)
}
@ -53,15 +52,14 @@ public class RealNDField(
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun mapIndexed(
arg: NDStructure<Double>,
override inline fun NDStructure<Double>.mapIndexed(
transform: RealField.(index: IntArray, Double) -> Double,
): NDBuffer<Double> = NDBuffer(
strides,
buffer = RealBuffer(strides.linearSize) { offset ->
RealField.transform(
strides.index(offset),
arg.buffer.array[offset]
buffer.array[offset]
)
})
@ -77,25 +75,25 @@ public class RealNDField(
return NDBuffer(strides, buffer)
}
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = map(arg) { power(it, pow) }
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = arg.map { power(it, pow) }
override fun exp(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { exp(it) }
override fun exp(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { exp(it) }
override fun ln(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { ln(it) }
override fun ln(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { ln(it) }
override fun sin(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { sin(it) }
override fun cos(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { cos(it) }
override fun tan(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { tan(it) }
override fun asin(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { asin(it) }
override fun acos(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { acos(it) }
override fun atan(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { atan(it) }
override fun sin(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { sin(it) }
override fun cos(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { cos(it) }
override fun tan(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { tan(it) }
override fun asin(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { asin(it) }
override fun acos(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { acos(it) }
override fun atan(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { atan(it) }
override fun sinh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { sinh(it) }
override fun cosh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { cosh(it) }
override fun tanh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { tanh(it) }
override fun asinh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { asinh(it) }
override fun acosh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { acosh(it) }
override fun atanh(arg: NDStructure<Double>): NDBuffer<Double> = map(arg) { atanh(it) }
override fun sinh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { sinh(it) }
override fun cosh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { cosh(it) }
override fun tanh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { tanh(it) }
override fun asinh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { asinh(it) }
override fun acosh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { acosh(it) }
override fun atanh(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { atanh(it) }
}
public fun NDAlgebra.Companion.real(vararg shape: Int): RealNDField = RealNDField(shape)

View File

@ -1,6 +1,7 @@
package kscience.kmath.nd
import kscience.kmath.linear.Matrix
import kscience.kmath.linear.BufferMatrix
import kscience.kmath.linear.RealMatrixContext
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.VirtualBuffer
@ -58,9 +59,9 @@ public interface Structure2D<T> : NDStructure<T> {
rows: Int,
columns: Int,
crossinline init: (i: Int, j: Int) -> Double,
): Matrix<Double> = NDAlgebra.real(rows, columns).produce { (i, j) ->
): BufferMatrix<Double> = RealMatrixContext.produce(rows,columns) { i, j ->
init(i, j)
}.as2D()
}
}
}

View File

@ -140,7 +140,7 @@ public fun RealMatrix.min(): Double? = elements().map { (_, value) -> value }.mi
public fun RealMatrix.max(): Double? = elements().map { (_, value) -> value }.maxOrNull()
public fun RealMatrix.average(): Double = elements().map { (_, value) -> value }.average()
public inline fun RealMatrix.map(transform: (Double) -> Double): RealMatrix =
public inline fun RealMatrix.map(crossinline transform: (Double) -> Double): RealMatrix =
MatrixContext.real.produce(rowNum, colNum) { i, j ->
transform(get(i, j))
}

View File

@ -44,18 +44,17 @@ public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C> {
return struct
}
public override fun map(arg: NDStructure<T>, transform: C.(T) -> T): Nd4jArrayStructure<T> {
val newStruct = arg.ndArray.dup().wrap()
public override fun NDStructure<T>.map(transform: C.(T) -> T): Nd4jArrayStructure<T> {
val newStruct = ndArray.dup().wrap()
newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) }
return newStruct
}
public override fun mapIndexed(
arg: NDStructure<T>,
public override fun NDStructure<T>.mapIndexed(
transform: C.(index: IntArray, T) -> T,
): Nd4jArrayStructure<T> {
val new = Nd4j.create(*shape).wrap()
new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, arg[idx]) }
val new = Nd4j.create(*this@Nd4jArrayAlgebra.shape).wrap()
new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, this[idx]) }
return new
}

View File

@ -1,7 +1,7 @@
package kscience.kmath.nd4j
import org.nd4j.linalg.factory.Nd4j
import kscience.kmath.operations.invoke
import org.nd4j.linalg.factory.Nd4j
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.fail
@ -20,7 +20,7 @@ internal class Nd4jArrayAlgebraTest {
@Test
fun testMap() {
val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { map(one) { it + it * 2 } }
val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { one.map() { it + it * 2 } }
val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure()
expected[intArrayOf(0, 0)] = 3
expected[intArrayOf(0, 1)] = 3

View File

@ -1,7 +1,10 @@
package kscience.kmath.viktor
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.nd.*
import kscience.kmath.operations.ExtendedField
import kscience.kmath.operations.RealField
import kscience.kmath.operations.RingWithNumbers
import org.jetbrains.bio.viktor.F64Array
@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
@ -20,8 +23,10 @@ public inline class ViktorNDStructure(public val f64Buffer: F64Array) : MutableN
public fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this)
@OptIn(UnstableKMathAPI::class)
@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
public class ViktorNDField(public override val shape: IntArray) : NDField<Double, RealField> {
public class ViktorNDField(public override val shape: IntArray) : NDField<Double, RealField>,
RingWithNumbers<NDStructure<Double>>, ExtendedField<NDStructure<Double>> {
public val NDStructure<Double>.f64Buffer: F64Array
get() = when {
@ -50,26 +55,25 @@ public class ViktorNDField(public override val shape: IntArray) : NDField<Double
}
}.asStructure()
public override fun map(arg: NDStructure<Double>, transform: RealField.(Double) -> Double): ViktorNDStructure =
F64Array(*shape).apply {
public override fun NDStructure<Double>.map(transform: RealField.(Double) -> Double): ViktorNDStructure =
F64Array(*this@ViktorNDField.shape).apply {
this@ViktorNDField.strides.indices().forEach { index ->
set(value = RealField.transform(arg[index]), indices = index)
set(value = RealField.transform(this@map[index]), indices = index)
}
}.asStructure()
public override fun mapIndexed(
arg: NDStructure<Double>,
transform: RealField.(index: IntArray, Double) -> Double
): ViktorNDStructure = F64Array(*shape).apply {
public override fun NDStructure<Double>.mapIndexed(
transform: RealField.(index: IntArray, Double) -> Double,
): ViktorNDStructure = F64Array(*this@ViktorNDField.shape).apply {
this@ViktorNDField.strides.indices().forEach { index ->
set(value = RealField.transform(index, arg[index]), indices = index)
set(value = RealField.transform(index, this@mapIndexed[index]), indices = index)
}
}.asStructure()
public override fun combine(
a: NDStructure<Double>,
b: NDStructure<Double>,
transform: RealField.(Double, Double) -> Double
transform: RealField.(Double, Double) -> Double,
): ViktorNDStructure = F64Array(*shape).apply {
this@ViktorNDField.strides.indices().forEach { index ->
set(value = RealField.transform(a[index], b[index]), indices = index)
@ -93,6 +97,25 @@ public class ViktorNDField(public override val shape: IntArray) : NDField<Double
public override inline fun NDStructure<Double>.plus(arg: Double): ViktorNDStructure =
(f64Buffer.plus(arg)).asStructure()
override fun number(value: Number): ViktorNDStructure =
F64Array.full(init = value.toDouble(), shape = shape).asStructure()
override fun sin(arg: NDStructure<Double>): ViktorNDStructure = arg.map { sin(it) }
override fun cos(arg: NDStructure<Double>): ViktorNDStructure = arg.map { cos(it) }
override fun asin(arg: NDStructure<Double>): ViktorNDStructure = arg.map { asin(it) }
override fun acos(arg: NDStructure<Double>): ViktorNDStructure = arg.map { acos(it) }
override fun atan(arg: NDStructure<Double>): ViktorNDStructure = arg.map { atan(it) }
override fun power(arg: NDStructure<Double>, pow: Number): ViktorNDStructure = arg.map { it.pow(pow) }
override fun exp(arg: NDStructure<Double>): ViktorNDStructure = arg.f64Buffer.exp().asStructure()
override fun ln(arg: NDStructure<Double>): ViktorNDStructure = arg.f64Buffer.log().asStructure()
}
public fun ViktorNDField(vararg shape: Int): ViktorNDField = ViktorNDField(shape)