Refactor/ndalgebra #197

Merged
altavir merged 11 commits from refactor/ndalgebra into dev 2021-01-28 20:13:39 +03:00
69 changed files with 1301 additions and 1471 deletions

View File

@ -36,6 +36,7 @@
- `NumericAlgebra` moved outside of regular algebra chain (`Ring` no longer implements it). - `NumericAlgebra` moved outside of regular algebra chain (`Ring` no longer implements it).
- Features moved to NDStructure and became transparent. - Features moved to NDStructure and became transparent.
- Capitalization of LUP in many names changed to Lup. - Capitalization of LUP in many names changed to Lup.
- Refactored `NDStructure` algebra to be more simple, preferring under-the-hood conversion to explicit NDStructure types
### Deprecated ### Deprecated

View File

@ -114,7 +114,7 @@ submit a feature request if you want something to be implemented first.
> >
> **Features:** > **Features:**
> - [algebras](kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : Algebraic structures: contexts and elements > - [algebras](kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : Algebraic structures: contexts and elements
> - [nd](kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt) : Many-dimensional structures > - [nd](kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDStructure.kt) : Many-dimensional structures
> - [buffers](kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : One-dimensional structure > - [buffers](kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : One-dimensional structure
> - [expressions](kmath-core/src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions > - [expressions](kmath-core/src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions
> - [domains](kmath-core/src/commonMain/kotlin/kscience/kmath/domains) : Domains > - [domains](kmath-core/src/commonMain/kotlin/kscience/kmath/domains) : Domains

View File

@ -4,7 +4,7 @@ plugins {
id("ru.mipt.npm.project") id("ru.mipt.npm.project")
} }
internal val kmathVersion: String by extra("0.2.0-dev-5") internal val kmathVersion: String by extra("0.2.0-dev-6")
internal val bintrayRepo: String by extra("kscience") internal val bintrayRepo: String by extra("kscience")
internal val githubProject: String by extra("kmath") internal val githubProject: String by extra("kmath")

View File

@ -68,11 +68,12 @@ benchmark {
targets.register("benchmarks") targets.register("benchmarks")
// This one matches sourceSet name above // This one matches sourceSet name above
configurations.register("fast") { configurations.register("dot") {
warmups = 1 // number of warmup iterations warmups = 1 // number of warmup iterations
iterations = 3 // number of iterations iterations = 3 // number of iterations
iterationTime = 500 // time in seconds per iteration iterationTime = 500 // time in seconds per iteration
iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds
include("DotBenchmark")
} }
} }

View File

@ -3,14 +3,12 @@ package kscience.kmath.benchmarks
import kotlinx.benchmark.Benchmark import kotlinx.benchmark.Benchmark
import kscience.kmath.commons.linear.CMMatrixContext import kscience.kmath.commons.linear.CMMatrixContext
import kscience.kmath.ejml.EjmlMatrixContext import kscience.kmath.ejml.EjmlMatrixContext
import kscience.kmath.linear.BufferMatrixContext import kscience.kmath.linear.BufferMatrixContext
import kscience.kmath.linear.Matrix
import kscience.kmath.linear.RealMatrixContext import kscience.kmath.linear.RealMatrixContext
import kscience.kmath.linear.real
import kscience.kmath.operations.RealField import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.Buffer import kscience.kmath.structures.Buffer
import kscience.kmath.structures.Matrix
import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State import org.openjdk.jmh.annotations.State
import kotlin.random.Random import kotlin.random.Random
@ -33,38 +31,35 @@ class DotBenchmark {
} }
@Benchmark @Benchmark
fun commonsMathMultiplication() { fun cmDot() {
CMMatrixContext { CMMatrixContext {
cmMatrix1 dot cmMatrix2 cmMatrix1 dot cmMatrix2
} }
} }
@Benchmark @Benchmark
fun ejmlMultiplication() { fun ejmlDot() {
EjmlMatrixContext { EjmlMatrixContext {
ejmlMatrix1 dot ejmlMatrix2 ejmlMatrix1 dot ejmlMatrix2
} }
} }
@Benchmark @Benchmark
fun ejmlMultiplicationwithConversion() { fun ejmlDotWithConversion() {
EjmlMatrixContext { EjmlMatrixContext {
val ejmlMatrix1 = matrix1.toEjml() matrix1 dot matrix2
val ejmlMatrix2 = matrix2.toEjml()
ejmlMatrix1 dot ejmlMatrix2
} }
} }
@Benchmark @Benchmark
fun bufferedMultiplication() { fun bufferedDot() {
BufferMatrixContext(RealField, Buffer.Companion::real).invoke { BufferMatrixContext(RealField, Buffer.Companion::real).invoke {
matrix1 dot matrix2 matrix1 dot matrix2
} }
} }
@Benchmark @Benchmark
fun realMultiplication() { fun realDot() {
RealMatrixContext { RealMatrixContext {
matrix1 dot matrix2 matrix1 dot matrix2
} }

View File

@ -1,8 +1,9 @@
package kscience.kmath.benchmarks package kscience.kmath.benchmarks
import kscience.kmath.nd.*
import kscience.kmath.operations.RealField import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.* import kscience.kmath.structures.Buffer
import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State import org.openjdk.jmh.annotations.State
@ -11,22 +12,16 @@ import org.openjdk.jmh.annotations.State
internal class NDFieldBenchmark { internal class NDFieldBenchmark {
@Benchmark @Benchmark
fun autoFieldAdd() { fun autoFieldAdd() {
bufferedField { autoField {
var res: NDBuffer<Double> = one var res: NDStructure<Double> = one
repeat(n) { res += one } repeat(n) { res += one }
} }
} }
@Benchmark
fun autoElementAdd() {
var res = genericField.one
repeat(n) { res += 1.0 }
}
@Benchmark @Benchmark
fun specializedFieldAdd() { fun specializedFieldAdd() {
specializedField { specializedField {
var res: NDBuffer<Double> = one var res: NDStructure<Double> = one
repeat(n) { res += 1.0 } repeat(n) { res += 1.0 }
} }
} }
@ -35,16 +30,16 @@ internal class NDFieldBenchmark {
@Benchmark @Benchmark
fun boxingFieldAdd() { fun boxingFieldAdd() {
genericField { genericField {
var res: NDBuffer<Double> = one var res: NDStructure<Double> = one
repeat(n) { res += one } repeat(n) { res += 1.0 }
} }
} }
companion object { companion object {
const val dim: Int = 1000 const val dim: Int = 1000
const val n: Int = 100 const val n: Int = 100
val bufferedField: BufferedNDField<Double, RealField> = NDField.auto(RealField, dim, dim) val autoField = NDAlgebra.auto(RealField, dim, dim)
val specializedField: RealNDField = NDField.real(dim, dim) val specializedField: RealNDField = NDAlgebra.real(dim, dim)
val genericField: BoxingNDField<Double, RealField> = NDField.boxing(RealField, dim, dim) val genericField = NDAlgebra.field(RealField, Buffer.Companion::boxing, dim, dim)
} }
} }

View File

@ -1,10 +1,8 @@
package kscience.kmath.benchmarks package kscience.kmath.benchmarks
import kscience.kmath.nd.*
import kscience.kmath.operations.RealField import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.BufferedNDField
import kscience.kmath.structures.NDField
import kscience.kmath.structures.RealNDField
import kscience.kmath.viktor.ViktorNDField import kscience.kmath.viktor.ViktorNDField
import org.jetbrains.bio.viktor.F64Array import org.jetbrains.bio.viktor.F64Array
import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Benchmark
@ -17,15 +15,23 @@ internal class ViktorBenchmark {
final val n: Int = 100 final val n: Int = 100
// automatically build context most suited for given type. // automatically build context most suited for given type.
final val autoField: BufferedNDField<Double, RealField> = NDField.auto(RealField, dim, dim) final val autoField: NDField<Double, RealField> = NDAlgebra.auto(RealField, dim, dim)
final val realField: RealNDField = NDField.real(dim, dim) final val realField: RealNDField = NDAlgebra.real(dim, dim)
final val viktorField: ViktorNDField = ViktorNDField(intArrayOf(dim, dim)) final val viktorField: ViktorNDField = ViktorNDField(dim, dim)
@Benchmark @Benchmark
fun automaticFieldAddition() { fun automaticFieldAddition() {
autoField { autoField {
var res = one var res: NDStructure<Double> = one
repeat(n) { res += one } repeat(n) { res += 1.0 }
}
}
@Benchmark
fun realFieldAddition() {
realField {
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
} }
} }
@ -33,7 +39,7 @@ internal class ViktorBenchmark {
fun viktorFieldAddition() { fun viktorFieldAddition() {
viktorField { viktorField {
var res = one var res = one
repeat(n) { res += one } repeat(n) { res += 1.0 }
} }
} }
@ -43,22 +49,4 @@ internal class ViktorBenchmark {
var res = one var res = one
repeat(n) { res = res + one } repeat(n) { res = res + one }
} }
@Benchmark
fun realFieldLog() {
realField {
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)
var res: F64Array
repeat(n) {
res = fortyTwo.log()
}
}
} }

View File

@ -0,0 +1,49 @@
package kscience.kmath.benchmarks
import kscience.kmath.nd.*
import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke
import kscience.kmath.viktor.ViktorNDField
import org.jetbrains.bio.viktor.F64Array
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State
@State(Scope.Benchmark)
internal class ViktorLogBenchmark {
final val dim: Int = 1000
final val n: Int = 100
// automatically build context most suited for given type.
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))
@Benchmark
fun realFieldLog() {
realField {
val fortyTwo = produce { 42.0 }
var res = one
repeat(n) { res = ln(fortyTwo) }
}
}
@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)
var res: F64Array
repeat(n) {
res = fortyTwo.log()
}
}
}
CommanderTvis commented 2021-01-24 11:29:54 +03:00 (Migrated from github.com)
Review

Missing newline.

Missing newline.

View File

@ -1,18 +1,17 @@
package kscience.kmath.operations package kscience.kmath.operations
import kscience.kmath.structures.NDElement import kscience.kmath.nd.NDAlgebra
import kscience.kmath.structures.NDField import kscience.kmath.nd.complex
import kscience.kmath.structures.complex
fun main() { fun main() {
// 2d element // 2d element
val element = NDElement.complex(2, 2) { (i,j) -> val element = NDAlgebra.complex(2, 2).produce { (i,j) ->
Complex(i.toDouble() - j.toDouble(), i.toDouble() + j.toDouble()) Complex(i.toDouble() - j.toDouble(), i.toDouble() + j.toDouble())
} }
println(element) println(element)
// 1d element operation // 1d element operation
val result = with(NDField.complex(8)) { val result = with(NDAlgebra.complex(8)) {
val a = produce { (it) -> i * it - it.toDouble() } val a = produce { (it) -> i * it - it.toDouble() }
val b = 3 val b = 3
val c = Complex(1.0, 1.0) val c = Complex(1.0, 1.0)

View File

@ -1,6 +1,9 @@
@file:Suppress("unused")
package kscience.kmath.structures package kscience.kmath.structures
import kscience.kmath.linear.transpose import kscience.kmath.linear.transpose
import kscience.kmath.nd.*
import kscience.kmath.operations.Complex import kscience.kmath.operations.Complex
import kscience.kmath.operations.ComplexField import kscience.kmath.operations.ComplexField
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
@ -10,12 +13,12 @@ fun main() {
val dim = 1000 val dim = 1000
val n = 1000 val n = 1000
val realField = NDField.real(dim, dim) val realField = NDAlgebra.real(dim, dim)
val complexField: ComplexNDField = NDField.complex(dim, dim) val complexField: ComplexNDField = NDAlgebra.complex(dim, dim)
val realTime = measureTimeMillis { val realTime = measureTimeMillis {
realField { realField {
var res: NDBuffer<Double> = one var res: NDStructure<Double> = one
repeat(n) { repeat(n) {
res += 1.0 res += 1.0
} }
@ -26,8 +29,10 @@ fun main() {
val complexTime = measureTimeMillis { val complexTime = measureTimeMillis {
complexField { complexField {
var res: NDBuffer<Complex> = one var res: NDStructure<Complex> = one
repeat(n) { res += 1.0 } repeat(n) {
res += 1.0
}
} }
} }

View File

@ -1,9 +1,11 @@
package kscience.kmath.structures package kscience.kmath.structures
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kscience.kmath.nd.*
import kscience.kmath.nd4j.Nd4jArrayField import kscience.kmath.nd4j.Nd4jArrayField
import kscience.kmath.operations.RealField import kscience.kmath.operations.RealField
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.viktor.ViktorNDField
import org.nd4j.linalg.factory.Nd4j import org.nd4j.linalg.factory.Nd4j
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
@ -22,42 +24,62 @@ fun main() {
val n = 1000 val n = 1000
// automatically build context most suited for given type. // automatically build context most suited for given type.
val autoField = NDField.auto(RealField, dim, dim) val autoField = NDAlgebra.auto(RealField, dim, dim)
// specialized nd-field for Double. It works as generic Double field as well // specialized nd-field for Double. It works as generic Double field as well
val specializedField = NDField.real(dim, dim) val realField = NDAlgebra.real(dim, dim)
//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.boxing(RealField, dim, dim) val boxingField = NDAlgebra.field(RealField, Buffer.Companion::boxing, dim, dim)
// Nd4j specialized field. // Nd4j specialized field.
val nd4jField = Nd4jArrayField.real(dim, dim) val nd4jField = Nd4jArrayField.real(dim, dim)
//viktor field
val viktorField = ViktorNDField(dim,dim)
//parallel processing based on Java Streams
val parallelField = NDAlgebra.realWithStream(dim,dim)
measureAndPrint("Automatic field addition") { measureAndPrint("Boxing addition") {
autoField { boxingField {
var res: NDBuffer<Double> = one var res: NDStructure<Double> = one
repeat(n) { res += 1.0 } repeat(n) { res += 1.0 }
} }
} }
measureAndPrint("Element addition") {
var res = genericField.one
repeat(n) { res += 1.0 }
}
measureAndPrint("Specialized addition") { measureAndPrint("Specialized addition") {
specializedField { realField {
var res: NDBuffer<Double> = one var res: NDStructure<Double> = one
repeat(n) { res += 1.0 } repeat(n) { res += 1.0 }
} }
} }
measureAndPrint("Nd4j specialized addition") { measureAndPrint("Nd4j specialized addition") {
nd4jField { nd4jField {
var res = one var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
measureAndPrint("Viktor addition") {
viktorField {
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
measureAndPrint("Parallel stream addition") {
parallelField {
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 }
}
}
measureAndPrint("Automatic field addition") {
autoField {
var res: NDStructure<Double> = one
repeat(n) { res += 1.0 } repeat(n) { res += 1.0 }
} }
} }
measureAndPrint("Lazy addition") { measureAndPrint("Lazy addition") {
val res = specializedField.one.mapAsync(GlobalScope) { val res = realField.one.mapAsync(GlobalScope) {
var c = 0.0 var c = 0.0
repeat(n) { repeat(n) {
c += 1.0 c += 1.0
@ -67,14 +89,4 @@ fun main() {
res.elements().forEach { it.second } res.elements().forEach { it.second }
} }
measureAndPrint("Generic addition") {
//genericField.run(action)
genericField {
var res: NDBuffer<Double> = one
repeat(n) {
res += 1.0 // couldn't avoid using `one` due to resolution ambiguity }
}
}
}
} }

View File

@ -0,0 +1,103 @@
package kscience.kmath.structures
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 java.util.*
import java.util.stream.IntStream
/**
* A demonstration implementation of NDField over Real using Java [DoubleStream] for parallel execution
*/
@OptIn(UnstableKMathAPI::class)
class StreamRealNDField(
override val shape: IntArray,
) : NDField<Double, RealField>,
RingWithNumbers<NDStructure<Double>>,
ExtendedField<NDStructure<Double>> {
private val strides = DefaultStrides(shape)
override val elementContext: RealField get() = RealField
override val zero: NDBuffer<Double> by lazy { produce { zero } }
override val one: NDBuffer<Double> by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Double> {
val d = value.toDouble() // minimize conversions
return produce { d }
}
private val NDStructure<Double>.buffer: RealBuffer
get() = when {
!shape.contentEquals(this@StreamRealNDField.shape) -> throw ShapeMismatchException(
this@StreamRealNDField.shape,
shape
)
this is NDBuffer && this.strides == this@StreamRealNDField.strides -> this.buffer as RealBuffer
else -> RealBuffer(strides.linearSize) { offset -> get(strides.index(offset)) }
}
override fun produce(initializer: RealField.(IntArray) -> Double): NDBuffer<Double> {
val array = IntStream.range(0, strides.linearSize).parallel().mapToDouble { offset ->
val index = strides.index(offset)
RealField.initializer(index)
}.toArray()
return NDBuffer(strides, array.asBuffer())
}
override fun NDStructure<Double>.map(
transform: RealField.(Double) -> Double,
): NDBuffer<Double> {
val array = Arrays.stream(buffer.array).parallel().map { RealField.transform(it) }.toArray()
return NDBuffer(strides, array.asBuffer())
}
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),
buffer.array[offset]
)
}.toArray()
return NDBuffer(strides, array.asBuffer())
}
override fun combine(
a: NDStructure<Double>,
b: NDStructure<Double>,
transform: RealField.(Double, Double) -> Double,
): NDBuffer<Double> {
val array = IntStream.range(0, strides.linearSize).parallel().mapToDouble { offset ->
RealField.transform(a.buffer.array[offset], b.buffer.array[offset])
}.toArray()
return NDBuffer(strides, array.asBuffer())
}
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = arg.map() { power(it, pow) }
override fun exp(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { exp(it) }
override fun ln(arg: NDStructure<Double>): NDBuffer<Double> = arg.map() { ln(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> = 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

@ -1,5 +1,7 @@
package kscience.kmath.structures package kscience.kmath.structures
import kscience.kmath.nd.DefaultStrides
import kscience.kmath.nd.NDBuffer
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
fun main() { fun main() {
@ -7,7 +9,7 @@ fun main() {
val array = DoubleArray(n * n) { 1.0 } val array = DoubleArray(n * n) { 1.0 }
val buffer = RealBuffer(array) val buffer = RealBuffer(array)
val strides = DefaultStrides(intArrayOf(n, n)) val strides = DefaultStrides(intArrayOf(n, n))
val structure = BufferNDStructure(strides, buffer) val structure = NDBuffer(strides, buffer)
measureTimeMillis { measureTimeMillis {
var res = 0.0 var res = 0.0

View File

@ -1,5 +1,7 @@
package kscience.kmath.structures package kscience.kmath.structures
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.mapToBuffer
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
fun main() { fun main() {

View File

@ -2,7 +2,6 @@ package kscience.kmath.commons.linear
import kscience.kmath.linear.* import kscience.kmath.linear.*
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.RealBuffer import kscience.kmath.structures.RealBuffer
import org.apache.commons.math3.linear.* import org.apache.commons.math3.linear.*
import kotlin.reflect.KClass import kotlin.reflect.KClass

View File

@ -1,7 +1,7 @@
package kscience.kmath.commons.linear package kscience.kmath.commons.linear
import kscience.kmath.linear.Matrix
import kscience.kmath.linear.Point import kscience.kmath.linear.Point
import kscience.kmath.structures.Matrix
import org.apache.commons.math3.linear.* import org.apache.commons.math3.linear.*
public enum class CMDecomposition { public enum class CMDecomposition {

View File

@ -3,7 +3,7 @@
The core features of KMath: The core features of KMath:
- [algebras](src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : Algebraic structures: contexts and elements - [algebras](src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : Algebraic structures: contexts and elements
- [nd](src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt) : Many-dimensional structures - [nd](src/commonMain/kotlin/kscience/kmath/nd/NDStructure.kt) : Many-dimensional structures
- [buffers](src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : One-dimensional structure - [buffers](src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : One-dimensional structure
- [expressions](src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions - [expressions](src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions
- [domains](src/commonMain/kotlin/kscience/kmath/domains) : Domains - [domains](src/commonMain/kotlin/kscience/kmath/domains) : Domains

View File

@ -1,8 +1,8 @@
package kscience.kmath.expressions package kscience.kmath.expressions
import kscience.kmath.linear.Point import kscience.kmath.linear.Point
import kscience.kmath.nd.Structure2D
import kscience.kmath.structures.BufferFactory import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.Structure2D
/** /**
* An environment to easy transform indexed variables to symbols and back. * An environment to easy transform indexed variables to symbols and back.

View File

@ -1,7 +1,19 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.Structure2D
import kscience.kmath.operations.Ring import kscience.kmath.operations.Ring
import kscience.kmath.structures.* import kscience.kmath.operations.invoke
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.asSequence
/**
* Alias for [Structure2D] with more familiar name.
*
* @param T the type of items.
*/
public typealias Matrix<T> = Structure2D<T>
/** /**
* Basic implementation of Matrix space based on [NDStructure] * Basic implementation of Matrix space based on [NDStructure]
@ -17,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) 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 public companion object
} }

View File

@ -1,7 +1,6 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.structures.Buffer import kscience.kmath.structures.Buffer
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.VirtualBuffer import kscience.kmath.structures.VirtualBuffer
public typealias Point<T> = Buffer<T> public typealias Point<T> = Buffer<T>

View File

@ -1,6 +1,7 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.nd.getFeature
import kscience.kmath.operations.* import kscience.kmath.operations.*
import kscience.kmath.structures.* import kscience.kmath.structures.*

View File

@ -1,6 +1,9 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.structures.* import kscience.kmath.nd.Structure2D
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.asBuffer
public class MatrixBuilder(public val rows: Int, public val columns: Int) { public class MatrixBuilder(public val rows: Int, public val columns: Int) {
public operator fun <T : Any> invoke(vararg elements: T): Matrix<T> { public operator fun <T : Any> invoke(vararg elements: T): Matrix<T> {
@ -22,7 +25,7 @@ public fun <T : Any> Structure2D.Companion.row(vararg values: T): Matrix<T> {
public inline fun <reified T : Any> Structure2D.Companion.row( public inline fun <reified T : Any> Structure2D.Companion.row(
size: Int, size: Int,
factory: BufferFactory<T> = Buffer.Companion::auto, factory: BufferFactory<T> = Buffer.Companion::auto,
noinline builder: (Int) -> T noinline builder: (Int) -> T,
): Matrix<T> { ): Matrix<T> {
val buffer = factory(size, builder) val buffer = factory(size, builder)
return BufferMatrix(1, size, buffer) return BufferMatrix(1, size, buffer)
@ -36,7 +39,7 @@ public fun <T : Any> Structure2D.Companion.column(vararg values: T): Matrix<T> {
public inline fun <reified T : Any> Structure2D.Companion.column( public inline fun <reified T : Any> Structure2D.Companion.column(
size: Int, size: Int,
factory: BufferFactory<T> = Buffer.Companion::auto, factory: BufferFactory<T> = Buffer.Companion::auto,
noinline builder: (Int) -> T noinline builder: (Int) -> T,
): Matrix<T> { ): Matrix<T> {
val buffer = factory(size, builder) val buffer = factory(size, builder)
return BufferMatrix(size, 1, buffer) return BufferMatrix(size, 1, buffer)

View File

@ -6,7 +6,6 @@ import kscience.kmath.operations.invoke
import kscience.kmath.operations.sum import kscience.kmath.operations.sum
import kscience.kmath.structures.Buffer import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.asSequence import kscience.kmath.structures.asSequence
/** /**

View File

@ -1,7 +1,5 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.structures.Matrix
/** /**
* A marker interface representing some properties of matrices or additional transformations of them. Features are used * A marker interface representing some properties of matrices or additional transformations of them. Features are used
* to optimize matrix operations performance in some cases or retrieve the APIs. * to optimize matrix operations performance in some cases or retrieve the APIs.

View File

@ -1,11 +1,10 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.nd.Structure2D
import kscience.kmath.nd.getFeature
import kscience.kmath.operations.Ring import kscience.kmath.operations.Ring
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.Structure2D
import kscience.kmath.structures.asBuffer import kscience.kmath.structures.asBuffer
import kscience.kmath.structures.getFeature
import kotlin.math.sqrt import kotlin.math.sqrt
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.safeCast import kotlin.reflect.safeCast
@ -39,7 +38,8 @@ public class MatrixWrapper<T : Any> internal constructor(
* Origin does not necessary store all features. * Origin does not necessary store all features.
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public val <T : Any> Matrix<T>.origin: Matrix<T> get() = (this as? MatrixWrapper)?.origin ?: this public val <T : Any> Matrix<T>.origin: Matrix<T>
get() = (this as? MatrixWrapper)?.origin ?: this
/** /**
* Add a single feature to a [Matrix] * Add a single feature to a [Matrix]
@ -60,12 +60,6 @@ public operator fun <T : Any> Matrix<T>.plus(newFeatures: Collection<MatrixFeatu
MatrixWrapper(this, newFeatures.toSet()) 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. * Build a square matrix from given elements.
*/ */

View File

@ -1,12 +1,10 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.RealBuffer import kscience.kmath.structures.RealBuffer
@Suppress("OVERRIDE_BY_INLINE")
public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> { public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
public override inline fun produce( public override fun produce(
rows: Int, rows: Int,
columns: Int, columns: Int,
initializer: (i: Int, j: Int) -> Double, initializer: (i: Int, j: Int) -> Double,
@ -15,7 +13,7 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
return BufferMatrix(rows, columns, buffer) 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) } produce(rowNum, colNum) { i, j -> get(i, j) }
} }
@ -25,10 +23,12 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
public override infix fun Matrix<Double>.dot(other: Matrix<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})" } 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 -> return produce(rowNum, other.colNum) { i, j ->
var res = 0.0 var res = 0.0
for (l in 0 until colNum) { for (l in 0 until colNum) {
res += get(i, l) * other.get(l, j) res += bufferMatrix[i, l] * otherBufferMatrix[l, j]
} }
res res
} }
@ -36,10 +36,11 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
public override infix fun Matrix<Double>.dot(vector: Point<Double>): Point<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})" } require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" }
val bufferMatrix = toBufferMatrix()
return RealBuffer(rowNum) { i -> return RealBuffer(rowNum) { i ->
var res = 0.0 var res = 0.0
for (j in 0 until colNum) { for (j in 0 until colNum) {
res += get(i, j) * vector[j] res += bufferMatrix[i, j] * vector[j]
} }
res res
} }
@ -48,17 +49,23 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
override fun add(a: Matrix<Double>, b: Matrix<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.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}" } 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 -> 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> = override fun Matrix<Double>.times(value: Double): BufferMatrix<Double> {
produce(rowNum, colNum) { i, j -> get(i, j) * value } val bufferMatrix = toBufferMatrix()
return produce(rowNum, colNum) { i, j -> bufferMatrix[i, j] * value }
}
override fun multiply(a: Matrix<Double>, k: Number): BufferMatrix<Double> = override fun multiply(a: Matrix<Double>, k: Number): BufferMatrix<Double> {
produce(a.rowNum, a.colNum) { i, j -> a[i, j] * k.toDouble() } val aBufferMatrix = a.toBufferMatrix()
return produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() }
}
} }

View File

@ -1,7 +1,5 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.structures.Matrix
public class VirtualMatrix<T : Any>( public class VirtualMatrix<T : Any>(
override val rowNum: Int, override val rowNum: Int,
override val colNum: Int, override val colNum: Int,

View File

@ -0,0 +1,134 @@
package kscience.kmath.nd
import kscience.kmath.nd.*
import kscience.kmath.operations.*
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
public interface BufferNDAlgebra<T, C> : NDAlgebra<T, C> {
public val strides: Strides
public val bufferFactory: BufferFactory<T>
override fun produce(initializer: C.(IntArray) -> T): NDBuffer<T> = NDBuffer(
strides,
bufferFactory(strides.linearSize) { offset ->
elementContext.initializer(strides.index(offset))
}
)
public val NDStructure<T>.buffer: Buffer<T>
get() = when {
!shape.contentEquals(this@BufferNDAlgebra.shape) -> throw ShapeMismatchException(
this@BufferNDAlgebra.shape,
shape
)
this is NDBuffer && this.strides == this@BufferNDAlgebra.strides -> this.buffer
else -> bufferFactory(strides.linearSize) { offset -> get(strides.index(offset)) }
}
override fun NDStructure<T>.map(transform: C.(T) -> T): NDBuffer<T> {
val buffer = bufferFactory(strides.linearSize) { offset ->
elementContext.transform(buffer[offset])
}
return NDBuffer(strides, buffer)
}
override fun NDStructure<T>.mapIndexed(transform: C.(index: IntArray, T) -> T): NDBuffer<T> {
val buffer = bufferFactory(strides.linearSize) { offset ->
elementContext.transform(
strides.index(offset),
buffer[offset]
)
}
return NDBuffer(strides, buffer)
}
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])
}
return NDBuffer(strides, buffer)
}
}
public open class BufferedNDSpace<T, R : Space<T>>(
final override val shape: IntArray,
final override val elementContext: R,
final override val bufferFactory: BufferFactory<T>,
) : NDSpace<T, R>, BufferNDAlgebra<T, R> {
override val strides: Strides = DefaultStrides(shape)
override val zero: NDBuffer<T> by lazy { produce { zero } }
}
public open class BufferedNDRing<T, R : Ring<T>>(
shape: IntArray,
elementContext: R,
bufferFactory: BufferFactory<T>,
) : BufferedNDSpace<T, R>(shape, elementContext, bufferFactory), NDRing<T, R> {
override val one: NDBuffer<T> by lazy { produce { one } }
}
public open class BufferedNDField<T, R : Field<T>>(
shape: IntArray,
elementContext: R,
bufferFactory: BufferFactory<T>,
) : BufferedNDRing<T, R>(shape, elementContext, bufferFactory), NDField<T, R>
// space factories
public fun <T, A : Space<T>> NDAlgebra.Companion.space(
space: A,
bufferFactory: BufferFactory<T>,
vararg shape: Int,
): BufferedNDSpace<T, A> = BufferedNDSpace(shape, space, bufferFactory)
public inline fun <T, A : Space<T>, R> A.ndSpace(
noinline bufferFactory: BufferFactory<T>,
vararg shape: Int,
action: BufferedNDSpace<T, A>.() -> R,
): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return NDAlgebra.space(this, bufferFactory, *shape).run(action)
}
//ring factories
public fun <T, A : Ring<T>> NDAlgebra.Companion.ring(
ring: A,
bufferFactory: BufferFactory<T>,
vararg shape: Int,
): BufferedNDRing<T, A> = BufferedNDRing(shape, ring, bufferFactory)
public inline fun <T, A : Ring<T>, R> A.ndRing(
noinline bufferFactory: BufferFactory<T>,
vararg shape: Int,
action: BufferedNDRing<T, A>.() -> R,
): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return NDAlgebra.ring(this, bufferFactory, *shape).run(action)
}
//field factories
public fun <T, A : Field<T>> NDAlgebra.Companion.field(
field: A,
bufferFactory: BufferFactory<T>,
vararg shape: Int,
): BufferedNDField<T, A> = BufferedNDField(shape, field, bufferFactory)
@Suppress("UNCHECKED_CAST")
public inline fun <reified T : Any, A : Field<T>> NDAlgebra.Companion.auto(
field: A,
vararg shape: Int,
): NDField<T, A> = when (field) {
RealField -> RealNDField(shape) as NDField<T, A>
else -> BufferedNDField(shape, field, Buffer.Companion::auto)
}
public inline fun <T, A : Field<T>, R> A.ndField(
noinline bufferFactory: BufferFactory<T>,
vararg shape: Int,
action: BufferedNDField<T, A>.() -> R,
): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return NDAlgebra.field(this, bufferFactory, *shape).run(action)
}

View File

@ -0,0 +1,113 @@
package kscience.kmath.nd
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
import kscience.kmath.structures.Buffer
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* An optimized nd-field for complex numbers
*/
@OptIn(UnstableKMathAPI::class)
public class ComplexNDField(
shape: IntArray,
) : BufferedNDField<Complex, ComplexField>(shape, ComplexField, Buffer.Companion::complex),
RingWithNumbers<NDStructure<Complex>>,
ExtendedField<NDStructure<Complex>> {
override val zero: NDBuffer<Complex> by lazy { produce { zero } }
override val one: NDBuffer<Complex> by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Complex> {
val d = value.toComplex() // minimize conversions
return produce { d }
}
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun map(
// arg: AbstractNDBuffer<Double>,
// transform: RealField.(Double) -> Double,
// ): RealNDElement {
// check(arg)
// val array = RealBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
// return BufferedNDFieldElement(this, array)
// }
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
// val array = RealBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
// return BufferedNDFieldElement(this, array)
// }
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun mapIndexed(
// arg: AbstractNDBuffer<Double>,
// transform: RealField.(index: IntArray, Double) -> Double,
// ): RealNDElement {
// check(arg)
// return BufferedNDFieldElement(
// this,
// RealBuffer(arg.strides.linearSize) { offset ->
// elementContext.transform(
// arg.strides.index(offset),
// arg.buffer[offset]
// )
// })
// }
//
// @Suppress("OVERRIDE_BY_INLINE")
// override inline fun combine(
// a: AbstractNDBuffer<Double>,
// b: AbstractNDBuffer<Double>,
// transform: RealField.(Double, Double) -> Double,
// ): RealNDElement {
// check(a, b)
// val buffer = RealBuffer(strides.linearSize) { offset ->
// elementContext.transform(a.buffer[offset], b.buffer[offset])
// }
// return BufferedNDFieldElement(this, buffer)
// }
override fun power(arg: NDStructure<Complex>, pow: Number): NDBuffer<Complex> = arg.map() { power(it, pow) }
override fun exp(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { exp(it) }
override fun ln(arg: NDStructure<Complex>): NDBuffer<Complex> = arg.map() { ln(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> = 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) }
}
/**
* Fast element production using function inlining
*/
public inline fun BufferedNDField<Complex, ComplexField>.produceInline(initializer: ComplexField.(Int) -> Complex): NDBuffer<Complex> {
contract { callsInPlace(initializer, InvocationKind.EXACTLY_ONCE) }
val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.initializer(offset) }
return NDBuffer(strides, buffer)
}
public fun NDAlgebra.Companion.complex(vararg shape: Int): ComplexNDField = ComplexNDField(shape)
/**
* Produce a context for n-dimensional operations inside this real field
*/
public inline fun <R> ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return ComplexNDField(shape).action()
}

View File

@ -0,0 +1,262 @@
package kscience.kmath.nd
import kscience.kmath.operations.Field
import kscience.kmath.operations.Ring
import kscience.kmath.operations.Space
import kscience.kmath.structures.*
/**
* An exception is thrown when the expected ans actual shape of NDArray differs.
*
* @property expected the expected shape.
* @property actual the actual shape.
*/
public class ShapeMismatchException(public val expected: IntArray, public val actual: IntArray) :
RuntimeException("Shape ${actual.contentToString()} doesn't fit in expected shape ${expected.contentToString()}.")
/**
* The base interface for all ND-algebra implementations.
*
* @param T the type of ND-structure element.
* @param C the type of the element context.
* @param N the type of the structure.
*/
public interface NDAlgebra<T, C> {
/**
* The shape of ND-structures this algebra operates on.
*/
public val shape: IntArray
/**
* The algebra over elements of ND structure.
*/
public val elementContext: C
/**
* Produces a new [N] structure using given initializer function.
*/
public fun produce(initializer: C.(IntArray) -> T): NDStructure<T>
/**
* Maps elements from one structure to another one by applying [transform] to them.
*/
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 NDStructure<T>.mapIndexed(transform: C.(index: IntArray, T) -> T): NDStructure<T>
/**
* Combines two structures into one.
*/
public fun combine(a: NDStructure<T>, b: NDStructure<T>, transform: C.(T, T) -> T): NDStructure<T>
/**
* Element-wise invocation of function working on [T] on a [NDStructure].
*/
public operator fun Function1<T, T>.invoke(structure: NDStructure<T>): NDStructure<T> =
structure.map() { value -> this@invoke(value) }
public companion object
}
/**
* Checks if given elements are consistent with this context.
*
* @param structures the structures to check.
* @return the array of valid structures.
*/
internal fun <T, C> NDAlgebra<T, C>.checkShape(vararg structures: NDStructure<T>): Array<out NDStructure<T>> = structures
.map(NDStructure<T>::shape)
.singleOrNull { !shape.contentEquals(it) }
?.let<IntArray, Array<out NDStructure<T>>> { throw ShapeMismatchException(shape, it) }
?: structures
/**
* Checks if given element is consistent with this context.
*
* @param element the structure to check.
* @return the valid structure.
*/
internal fun <T, C> NDAlgebra<T, C>.checkShape(element: NDStructure<T>): NDStructure<T> {
if (!element.shape.contentEquals(shape)) throw ShapeMismatchException(shape, element.shape)
return element
}
/**
* Space of [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param S the type of space of structure elements.
*/
public interface NDSpace<T, S : Space<T>> : Space<NDStructure<T>>, NDAlgebra<T, S> {
/**
* Element-wise addition.
*
* @param a the addend.
* @param b the augend.
* @return the sum.
*/
public override fun add(a: NDStructure<T>, b: NDStructure<T>): NDStructure<T> =
combine(a, b) { aValue, bValue -> add(aValue, bValue) }
/**
* Element-wise multiplication by scalar.
*
* @param a the multiplicand.
* @param k the multiplier.
* @return the product.
*/
public override fun multiply(a: NDStructure<T>, k: Number): NDStructure<T> = a.map() { multiply(it, k) }
// TODO move to extensions after KEEP-176
/**
* Adds an ND structure to an element of it.
*
* @receiver the addend.
* @param arg the augend.
* @return the sum.
*/
public operator fun NDStructure<T>.plus(arg: T): NDStructure<T> = this.map() { value -> add(arg, value) }
/**
* Subtracts an element from ND structure of it.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun NDStructure<T>.minus(arg: T): NDStructure<T> = this.map() { value -> add(arg, -value) }
/**
* Adds an element to ND structure of it.
*
* @receiver the addend.
* @param arg the augend.
* @return the sum.
*/
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.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun T.minus(arg: NDStructure<T>): NDStructure<T> = arg.map() { value -> add(-this@minus, value) }
public companion object
}
/**
* Ring of [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param R the type of ring of structure elements.
*/
public interface NDRing<T, R : Ring<T>> : Ring<NDStructure<T>>, NDSpace<T, R> {
/**
* Element-wise multiplication.
*
* @param a the multiplicand.
* @param b the multiplier.
* @return the product.
*/
public override fun multiply(a: NDStructure<T>, b: NDStructure<T>): NDStructure<T> =
combine(a, b) { aValue, bValue -> multiply(aValue, bValue) }
//TODO move to extensions after KEEP-176
/**
* Multiplies an ND structure by an element of it.
*
* @receiver the multiplicand.
* @param arg the multiplier.
* @return the product.
*/
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.
*
* @receiver the multiplicand.
* @param arg the multiplier.
* @return the product.
*/
public operator fun T.times(arg: NDStructure<T>): NDStructure<T> = arg.map() { value -> multiply(this@times, value) }
public companion object
}
/**
* Field of [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param F the type field of structure elements.
*/
public interface NDField<T, F : Field<T>> : Field<NDStructure<T>>, NDRing<T, F> {
/**
* Element-wise division.
*
* @param a the dividend.
* @param b the divisor.
* @return the quotient.
*/
public override fun divide(a: NDStructure<T>, b: NDStructure<T>): NDStructure<T> =
combine(a, b) { aValue, bValue -> divide(aValue, bValue) }
//TODO move to extensions after KEEP-176
/**
* Divides an ND structure by an element of it.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
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.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun T.div(arg: NDStructure<T>): NDStructure<T> = arg.map() { divide(it, this@div) }
// @ThreadLocal
// public companion object {
// private val realNDFieldCache: MutableMap<IntArray, RealNDField> = hashMapOf()
//
// /**
// * Create a nd-field for [Double] values or pull it from cache if it was created previously.
// */
// public fun real(vararg shape: Int): RealNDField = realNDFieldCache.getOrPut(shape) { RealNDField(shape) }
//
// /**
// * Create an ND field with boxing generic buffer.
// */
// public fun <T : Any, F : Field<T>> boxing(
// field: F,
// vararg shape: Int,
// bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
// ): BufferedNDField<T, F> = BufferedNDField(shape, field, bufferFactory)
//
// /**
// * Create a most suitable implementation for nd-field using reified class.
// */
// @Suppress("UNCHECKED_CAST")
// public inline fun <reified T : Any, F : Field<T>> auto(field: F, vararg shape: Int): NDField<T, F> =
// when {
// T::class == Double::class -> real(*shape) as NDField<T, F>
// T::class == Complex::class -> complex(*shape) as BufferedNDField<T, F>
// else -> BoxingNDField(shape, field, Buffer.Companion::auto)
// }
// }
}

View File

@ -1,6 +1,10 @@
package kscience.kmath.structures package kscience.kmath.nd
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.MutableBuffer
import kscience.kmath.structures.asSequence
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.native.concurrent.ThreadLocal import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -74,8 +78,8 @@ public interface NDStructure<T> {
strides: Strides, strides: Strides,
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing, bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
initializer: (IntArray) -> T, initializer: (IntArray) -> T,
): BufferNDStructure<T> = ): NDBuffer<T> =
BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) NDBuffer(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) })
/** /**
* Inline create NDStructure with non-boxing buffer implementation if it is possible * Inline create NDStructure with non-boxing buffer implementation if it is possible
@ -83,40 +87,40 @@ public interface NDStructure<T> {
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
strides: Strides, strides: Strides,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferNDStructure<T> = ): NDBuffer<T> =
BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) NDBuffer(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) })
public inline fun <T : Any> auto( public inline fun <T : Any> auto(
type: KClass<T>, type: KClass<T>,
strides: Strides, strides: Strides,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferNDStructure<T> = ): NDBuffer<T> =
BufferNDStructure(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) }) NDBuffer(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) })
public fun <T> build( public fun <T> build(
shape: IntArray, shape: IntArray,
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing, bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
initializer: (IntArray) -> T, initializer: (IntArray) -> T,
): BufferNDStructure<T> = build(DefaultStrides(shape), bufferFactory, initializer) ): NDBuffer<T> = build(DefaultStrides(shape), bufferFactory, initializer)
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
shape: IntArray, shape: IntArray,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferNDStructure<T> = ): NDBuffer<T> =
auto(DefaultStrides(shape), initializer) auto(DefaultStrides(shape), initializer)
@JvmName("autoVarArg") @JvmName("autoVarArg")
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
vararg shape: Int, vararg shape: Int,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferNDStructure<T> = ): NDBuffer<T> =
auto(DefaultStrides(shape), initializer) auto(DefaultStrides(shape), initializer)
public inline fun <T : Any> auto( public inline fun <T : Any> auto(
type: KClass<T>, type: KClass<T>,
vararg shape: Int, vararg shape: Int,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferNDStructure<T> = ): NDBuffer<T> =
auto(type, DefaultStrides(shape), initializer) auto(type, DefaultStrides(shape), initializer)
} }
} }
@ -156,7 +160,7 @@ public inline fun <T> MutableNDStructure<T>.mapInPlace(action: (IntArray, T) ->
*/ */
public interface Strides { public interface Strides {
/** /**
* Shape of NDstructure * Shape of NDStructure
*/ */
public val shape: IntArray public val shape: IntArray
@ -185,7 +189,9 @@ public interface Strides {
/** /**
* Iterate over ND indices in a natural order * Iterate over ND indices in a natural order
*/ */
public fun indices(): Sequence<IntArray> = (0 until linearSize).asSequence().map { index(it) } public fun indices(): Sequence<IntArray> = (0 until linearSize).asSequence().map {
index(it)
}
} }
/** /**
@ -211,9 +217,7 @@ public class DefaultStrides private constructor(override val shape: IntArray) :
} }
override fun offset(index: IntArray): Int = index.mapIndexed { i, value -> override fun offset(index: IntArray): Int = index.mapIndexed { i, value ->
if (value < 0 || value >= this.shape[i]) if (value < 0 || value >= shape[i]) throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})")
throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})")
value * strides[i] value * strides[i]
}.sum() }.sum()
@ -256,23 +260,29 @@ public class DefaultStrides private constructor(override val shape: IntArray) :
* Represents [NDStructure] over [Buffer]. * Represents [NDStructure] over [Buffer].
* *
* @param T the type of items. * @param T the type of items.
* @param strides The strides to access elements of [Buffer] by linear indices.
* @param buffer The underlying buffer.
*/ */
public abstract class NDBuffer<T> : NDStructure<T> { public open class NDBuffer<T>(
/** public val strides: Strides,
* The underlying buffer. buffer: Buffer<T>,
*/ ) : NDStructure<T> {
public abstract val buffer: Buffer<T>
/** init {
* The strides to access elements of [Buffer] by linear indices. if (strides.linearSize != buffer.size) {
*/ error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}")
public abstract val strides: Strides }
}
public open val buffer: Buffer<T> = buffer
override operator fun get(index: IntArray): T = buffer[strides.offset(index)] override operator fun get(index: IntArray): T = buffer[strides.offset(index)]
override val shape: IntArray get() = strides.shape override val shape: IntArray get() = strides.shape
override fun elements(): Sequence<Pair<IntArray, T>> = strides.indices().map { it to this[it] } override fun elements(): Sequence<Pair<IntArray, T>> = strides.indices().map {
it to this[it]
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return NDStructure.contentEquals(this, other as? NDStructure<*> ?: return false) return NDStructure.contentEquals(this, other as? NDStructure<*> ?: return false)
@ -297,46 +307,30 @@ public abstract class NDBuffer<T> : NDStructure<T> {
} }
return "NDBuffer(shape=${shape.contentToString()}, buffer=$bufferRepr)" return "NDBuffer(shape=${shape.contentToString()}, buffer=$bufferRepr)"
} }
} }
/** /**
* Boxing generic [NDStructure] * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [NDBuffer]
*/
public class BufferNDStructure<T>(
override val strides: Strides,
override val buffer: Buffer<T>,
) : NDBuffer<T>() {
init {
if (strides.linearSize != buffer.size) {
error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}")
}
}
}
/**
* Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure]
*/ */
public inline fun <T, reified R : Any> NDStructure<T>.mapToBuffer( public inline fun <T, reified R : Any> NDStructure<T>.mapToBuffer(
factory: BufferFactory<R> = Buffer.Companion::auto, factory: BufferFactory<R> = Buffer.Companion::auto,
crossinline transform: (T) -> R, crossinline transform: (T) -> R,
): BufferNDStructure<R> { ): NDBuffer<R> {
return if (this is BufferNDStructure<T>) return if (this is NDBuffer<T>)
BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) NDBuffer(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) })
else { else {
val strides = DefaultStrides(shape) val strides = DefaultStrides(shape)
BufferNDStructure(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) NDBuffer(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) })
} }
} }
/** /**
* Mutable ND buffer based on linear [MutableBuffer]. * Mutable ND buffer based on linear [MutableBuffer].
*/ */
public class MutableBufferNDStructure<T>( public class MutableNDBuffer<T>(
override val strides: Strides, strides: Strides,
override val buffer: MutableBuffer<T>, buffer: MutableBuffer<T>,
) : NDBuffer<T>(), MutableNDStructure<T> { ) : NDBuffer<T>(strides, buffer), MutableNDStructure<T> {
init { init {
require(strides.linearSize == buffer.size) { require(strides.linearSize == buffer.size) {
@ -344,6 +338,8 @@ public class MutableBufferNDStructure<T>(
} }
} }
override val buffer: MutableBuffer<T> = super.buffer as MutableBuffer<T>
override operator fun set(index: IntArray, value: T): Unit = buffer.set(strides.offset(index), value) override operator fun set(index: IntArray, value: T): Unit = buffer.set(strides.offset(index), value)
} }

View File

@ -0,0 +1,107 @@
package kscience.kmath.nd
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.ExtendedField
import kscience.kmath.operations.RealField
import kscience.kmath.operations.RingWithNumbers
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.RealBuffer
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(UnstableKMathAPI::class)
public class RealNDField(
shape: IntArray,
) : BufferedNDField<Double, RealField>(shape, RealField, Buffer.Companion::real),
RingWithNumbers<NDStructure<Double>>,
ExtendedField<NDStructure<Double>> {
override val zero: NDBuffer<Double> by lazy { produce { zero } }
override val one: NDBuffer<Double> by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Double> {
val d = value.toDouble() // minimize conversions
return produce { d }
}
override val NDStructure<Double>.buffer: RealBuffer
get() = when {
!shape.contentEquals(this@RealNDField.shape) -> throw ShapeMismatchException(
this@RealNDField.shape,
shape
)
this is NDBuffer && this.strides == this@RealNDField.strides -> this.buffer as RealBuffer
else -> RealBuffer(strides.linearSize) { offset -> get(strides.index(offset)) }
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun NDStructure<Double>.map(
transform: RealField.(Double) -> Double,
): NDBuffer<Double> {
val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(buffer.array[offset]) }
return NDBuffer(strides, buffer)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun produce(initializer: RealField.(IntArray) -> Double): NDBuffer<Double> {
val array = DoubleArray(strides.linearSize) { offset ->
val index = strides.index(offset)
RealField.initializer(index)
}
return NDBuffer(strides, RealBuffer(array))
}
@Suppress("OVERRIDE_BY_INLINE")
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),
buffer.array[offset]
)
})
@Suppress("OVERRIDE_BY_INLINE")
override inline fun combine(
a: NDStructure<Double>,
b: NDStructure<Double>,
transform: RealField.(Double, Double) -> Double,
): NDBuffer<Double> {
val buffer = RealBuffer(strides.linearSize) { offset ->
RealField.transform(a.buffer.array[offset], b.buffer.array[offset])
}
return NDBuffer(strides, buffer)
}
override fun power(arg: NDStructure<Double>, pow: Number): NDBuffer<Double> = arg.map { power(it, pow) }
override fun exp(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { exp(it) }
override fun ln(arg: NDStructure<Double>): NDBuffer<Double> = arg.map { ln(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> = 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)
/**
* Produce a context for n-dimensional operations inside this real field
*/
public inline fun <R> RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return RealNDField(shape).run(action)
}

View File

@ -0,0 +1,36 @@
package kscience.kmath.nd
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.RingWithNumbers
import kscience.kmath.operations.ShortRing
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.ShortBuffer
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(UnstableKMathAPI::class)
public class ShortNDRing(
shape: IntArray,
) : BufferedNDRing<Short, ShortRing>(shape, ShortRing, Buffer.Companion::auto),
RingWithNumbers<NDStructure<Short>> {
override val zero: NDBuffer<Short> by lazy { produce { zero } }
override val one: NDBuffer<Short> by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Short> {
val d = value.toShort() // minimize conversions
return produce { d }
}
}
/**
* Fast element production using function inlining.
*/
public inline fun BufferedNDRing<Short, ShortRing>.produceInline(crossinline initializer: ShortRing.(Int) -> Short): NDBuffer<Short> {
return NDBuffer(strides, ShortBuffer(ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) }))
}
public inline fun <R> ShortRing.nd(vararg shape: Int, action: ShortNDRing.() -> R): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return ShortNDRing(shape).run(action)
}

View File

@ -1,4 +1,7 @@
package kscience.kmath.structures package kscience.kmath.nd
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.asSequence
/** /**
* A structure that is guaranteed to be one-dimensional * A structure that is guaranteed to be one-dimensional
@ -34,7 +37,7 @@ private inline class Buffer1DWrapper<T>(val buffer: Buffer<T>) : Structure1D<T>
override val size: Int get() = buffer.size override val size: Int get() = buffer.size
override fun elements(): Sequence<Pair<IntArray, T>> = override fun elements(): Sequence<Pair<IntArray, T>> =
asSequence().mapIndexed { index, value -> intArrayOf(index) to value } buffer.asSequence().mapIndexed { index, value -> intArrayOf(index) to value }
override operator fun get(index: Int): T = buffer[index] override operator fun get(index: Int): T = buffer[index]
} }

View File

@ -1,4 +1,9 @@
package kscience.kmath.structures package kscience.kmath.nd
import kscience.kmath.linear.BufferMatrix
import kscience.kmath.linear.RealMatrixContext
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.VirtualBuffer
/** /**
* A structure that is guaranteed to be two-dimensional. * A structure that is guaranteed to be two-dimensional.
@ -49,7 +54,15 @@ public interface Structure2D<T> : NDStructure<T> {
for (j in 0 until colNum) yield(intArrayOf(i, j) to get(i, j)) for (j in 0 until colNum) yield(intArrayOf(i, j) to get(i, j))
} }
public companion object public companion object {
public inline fun real(
rows: Int,
columns: Int,
crossinline init: (i: Int, j: Int) -> Double,
): BufferMatrix<Double> = RealMatrixContext.produce(rows,columns) { i, j ->
init(i, j)
}
}
} }
/** /**
@ -73,10 +86,3 @@ public fun <T> NDStructure<T>.as2D(): Structure2D<T> = if (shape.size == 2)
Structure2DWrapper(this) Structure2DWrapper(this)
else else
error("Can't create 2d-structure from ${shape.size}d-structure") error("Can't create 2d-structure from ${shape.size}d-structure")
/**
* Alias for [Structure2D] with more familiar name.
*
* @param T the type of items.
*/
public typealias Matrix<T> = Structure2D<T>

View File

@ -1,9 +1,12 @@
package kscience.kmath.operations package kscience.kmath.operations
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.nd.BufferedNDRing
import kscience.kmath.nd.NDAlgebra
import kscience.kmath.operations.BigInt.Companion.BASE import kscience.kmath.operations.BigInt.Companion.BASE
import kscience.kmath.operations.BigInt.Companion.BASE_SIZE import kscience.kmath.operations.BigInt.Companion.BASE_SIZE
import kscience.kmath.structures.* import kscience.kmath.structures.Buffer
import kscience.kmath.structures.MutableBuffer
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -462,10 +465,5 @@ public inline fun Buffer.Companion.bigInt(size: Int, initializer: (Int) -> BigIn
public inline fun MutableBuffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): MutableBuffer<BigInt> = public inline fun MutableBuffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): MutableBuffer<BigInt> =
boxing(size, initializer) boxing(size, initializer)
public fun NDAlgebra.Companion.bigInt(vararg shape: Int): BoxingNDRing<BigInt, BigIntField> = public fun NDAlgebra.Companion.bigInt(vararg shape: Int): BufferedNDRing<BigInt, BigIntField> =
BoxingNDRing(shape, BigIntField, Buffer.Companion::bigInt) BufferedNDRing(shape, BigIntField, Buffer.Companion::bigInt)
public fun NDElement.Companion.bigInt(
vararg shape: Int,
initializer: BigIntField.(IntArray) -> BigInt
): BufferedNDRingElement<BigInt, BigIntField> = NDAlgebra.bigInt(*shape).produce(initializer)

View File

@ -1,81 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.Field
import kscience.kmath.operations.FieldElement
public class BoxingNDField<T, F : Field<T>>(
public override val shape: IntArray,
public override val elementContext: F,
public val bufferFactory: BufferFactory<T>
) : BufferedNDField<T, F> {
public override val zero: BufferedNDFieldElement<T, F> by lazy { produce { zero } }
public override val one: BufferedNDFieldElement<T, F> by lazy { produce { one } }
public override val strides: Strides = DefaultStrides(shape)
public fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T> =
bufferFactory(size, initializer)
public override fun check(vararg elements: NDBuffer<T>): Array<out NDBuffer<T>> {
require(elements.all { it.strides == strides }) { "Element strides are not the same as context strides" }
return elements
}
public override fun produce(initializer: F.(IntArray) -> T): BufferedNDFieldElement<T, F> =
BufferedNDFieldElement(
this,
buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) })
public override fun map(arg: NDBuffer<T>, transform: F.(T) -> T): BufferedNDFieldElement<T, F> {
check(arg)
return BufferedNDFieldElement(
this,
buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) })
// val buffer = arg.buffer.transform { _, value -> elementContext.transform(value) }
// return BufferedNDFieldElement(this, buffer)
}
public override fun mapIndexed(
arg: NDBuffer<T>,
transform: F.(index: IntArray, T) -> T
): BufferedNDFieldElement<T, F> {
check(arg)
return BufferedNDFieldElement(
this,
buildBuffer(arg.strides.linearSize) { offset ->
elementContext.transform(
arg.strides.index(offset),
arg.buffer[offset]
)
})
// val buffer =
// arg.buffer.transform { offset, value -> elementContext.transform(arg.strides.index(offset), value) }
// return BufferedNDFieldElement(this, buffer)
}
public override fun combine(
a: NDBuffer<T>,
b: NDBuffer<T>,
transform: F.(T, T) -> T
): BufferedNDFieldElement<T, F> {
check(a, b)
return BufferedNDFieldElement(
this,
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
}
public override fun NDBuffer<T>.toElement(): FieldElement<NDBuffer<T>, *, out BufferedNDField<T, F>> =
BufferedNDFieldElement(this@BoxingNDField, buffer)
}
public inline fun <T : Any, F : Field<T>, R> F.nd(
noinline bufferFactory: BufferFactory<T>,
vararg shape: Int,
action: NDField<T, F, *>.() -> R
): R {
val ndfield = NDField.boxing(this, *shape, bufferFactory = bufferFactory)
return ndfield.action()
}

View File

@ -1,71 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.Ring
import kscience.kmath.operations.RingElement
public class BoxingNDRing<T, R : Ring<T>>(
override val shape: IntArray,
override val elementContext: R,
public val bufferFactory: BufferFactory<T>
) : BufferedNDRing<T, R> {
override val strides: Strides = DefaultStrides(shape)
override val zero: BufferedNDRingElement<T, R> by lazy { produce { zero } }
override val one: BufferedNDRingElement<T, R> by lazy { produce { one } }
public fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T> = bufferFactory(size, initializer)
override fun check(vararg elements: NDBuffer<T>): Array<out NDBuffer<T>> {
if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides")
return elements
}
override fun produce(initializer: R.(IntArray) -> T): BufferedNDRingElement<T, R> =
BufferedNDRingElement(
this,
buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) })
override fun map(arg: NDBuffer<T>, transform: R.(T) -> T): BufferedNDRingElement<T, R> {
check(arg)
return BufferedNDRingElement(
this,
buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) })
// val buffer = arg.buffer.transform { _, value -> elementContext.transform(value) }
// return BufferedNDFieldElement(this, buffer)
}
override fun mapIndexed(
arg: NDBuffer<T>,
transform: R.(index: IntArray, T) -> T
): BufferedNDRingElement<T, R> {
check(arg)
return BufferedNDRingElement(
this,
buildBuffer(arg.strides.linearSize) { offset ->
elementContext.transform(
arg.strides.index(offset),
arg.buffer[offset]
)
})
// val buffer =
// arg.buffer.transform { offset, value -> elementContext.transform(arg.strides.index(offset), value) }
// return BufferedNDFieldElement(this, buffer)
}
override fun combine(
a: NDBuffer<T>,
b: NDBuffer<T>,
transform: R.(T, T) -> T
): BufferedNDRingElement<T, R> {
check(a, b)
return BufferedNDRingElement(
this,
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
}
override fun NDBuffer<T>.toElement(): RingElement<NDBuffer<T>, *, out BufferedNDRing<T, R>> =
BufferedNDRingElement(this@BoxingNDRing, buffer)
}

View File

@ -1,5 +1,10 @@
package kscience.kmath.structures package kscience.kmath.structures
import kscience.kmath.nd.DefaultStrides
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.Structure2D
import kscience.kmath.nd.as2D
/** /**
* A context that allows to operate on a [MutableBuffer] as on 2d array * A context that allows to operate on a [MutableBuffer] as on 2d array
*/ */

View File

@ -1,43 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.*
public interface BufferedNDAlgebra<T, C> : NDAlgebra<T, C, NDBuffer<T>> {
public val strides: Strides
public override fun check(vararg elements: NDBuffer<T>): Array<out NDBuffer<T>> {
require(elements.all { it.strides == strides }) { "Strides mismatch" }
return elements
}
/**
* 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
* indices.
*
* If the argument is [NDBuffer] with different strides structure, the new element will be produced.
*/
public fun NDStructure<T>.toBuffer(): NDBuffer<T> =
if (this is NDBuffer<T> && this.strides == this@BufferedNDAlgebra.strides)
this
else
produce { index -> this@toBuffer[index] }
/**
* Convert a buffer to element of this algebra
*/
public fun NDBuffer<T>.toElement(): MathElement<out BufferedNDAlgebra<T, C>>
}
public interface BufferedNDSpace<T, S : Space<T>> : NDSpace<T, S, NDBuffer<T>>, BufferedNDAlgebra<T, S> {
public override fun NDBuffer<T>.toElement(): SpaceElement<NDBuffer<T>, *, out BufferedNDSpace<T, S>>
}
public 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>>
}
public 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

@ -1,86 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.*
/**
* Base class for an element with context, containing strides
*/
public abstract class BufferedNDElement<T, C> : NDBuffer<T>(), NDElement<T, C, NDBuffer<T>> {
abstract override val context: BufferedNDAlgebra<T, C>
override val strides: Strides get() = context.strides
override val shape: IntArray get() = context.shape
}
public 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)
}
}
public 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)
}
}
public 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.
*/
public operator fun <T : Any, F : Field<T>> Function1<T, T>.invoke(ndElement: BufferedNDElement<T, F>): MathElement<out BufferedNDAlgebra<T, F>> =
ndElement.context.run { map(ndElement) { invoke(it) }.toElement() }
/* plus and minus */
/**
* Summation operation for [BufferedNDElement] and single element
*/
public operator fun <T : Any, F : Space<T>> BufferedNDElement<T, F>.plus(arg: T): NDElement<T, F, NDBuffer<T>> =
context.map(this) { it + arg }.wrap()
/**
* Subtraction operation between [BufferedNDElement] and single element
*/
public operator fun <T : Any, F : Space<T>> BufferedNDElement<T, F>.minus(arg: T): NDElement<T, F, NDBuffer<T>> =
context.map(this) { it - arg }.wrap()
/* prod and div */
/**
* Product operation for [BufferedNDElement] and single element
*/
public operator fun <T : Any, F : Ring<T>> BufferedNDElement<T, F>.times(arg: T): NDElement<T, F, NDBuffer<T>> =
context.map(this) { it * arg }.wrap()
/**
* Division operation between [BufferedNDElement] and single element
*/
public operator fun <T : Any, F : Field<T>> BufferedNDElement<T, F>.div(arg: T): NDElement<T, F, NDBuffer<T>> =
context.map(this) { it / arg }.wrap()

View File

@ -1,158 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
public typealias ComplexNDElement = BufferedNDFieldElement<Complex, ComplexField>
/**
* An optimized nd-field for complex numbers
*/
@OptIn(UnstableKMathAPI::class)
public class ComplexNDField(override val shape: IntArray) :
BufferedNDField<Complex, ComplexField>,
ExtendedNDField<Complex, ComplexField, NDBuffer<Complex>>,
RingWithNumbers<NDBuffer<Complex>>{
override val strides: Strides = DefaultStrides(shape)
override val elementContext: ComplexField get() = ComplexField
override val zero: ComplexNDElement by lazy { produce { zero } }
override val one: ComplexNDElement by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Complex> {
val c = value.toComplex()
return produce { c }
}
public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer<Complex> =
Buffer.complex(size) { initializer(it) }
/**
* Inline transform an NDStructure to another structure
*/
override fun map(
arg: NDBuffer<Complex>,
transform: ComplexField.(Complex) -> Complex,
): ComplexNDElement {
check(arg)
val array = buildBuffer(arg.strides.linearSize) { offset -> ComplexField.transform(arg.buffer[offset]) }
return BufferedNDFieldElement(this, array)
}
override fun produce(initializer: ComplexField.(IntArray) -> Complex): ComplexNDElement {
val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
return BufferedNDFieldElement(this, array)
}
override fun mapIndexed(
arg: NDBuffer<Complex>,
transform: ComplexField.(index: IntArray, Complex) -> Complex,
): ComplexNDElement {
check(arg)
return BufferedNDFieldElement(
this,
buildBuffer(arg.strides.linearSize) { offset ->
elementContext.transform(
arg.strides.index(offset),
arg.buffer[offset]
)
})
}
override fun combine(
a: NDBuffer<Complex>,
b: NDBuffer<Complex>,
transform: ComplexField.(Complex, Complex) -> Complex,
): ComplexNDElement {
check(a, b)
return BufferedNDFieldElement(
this,
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
}
override fun NDBuffer<Complex>.toElement(): FieldElement<NDBuffer<Complex>, *, out BufferedNDField<Complex, ComplexField>> =
BufferedNDFieldElement(this@ComplexNDField, buffer)
override fun power(arg: NDBuffer<Complex>, pow: Number): ComplexNDElement =
map(arg) { power(it, pow) }
override fun exp(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { exp(it) }
override fun ln(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { ln(it) }
override fun sin(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { sin(it) }
override fun cos(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { cos(it) }
override fun tan(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { tan(it) }
override fun asin(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { asin(it) }
override fun acos(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { acos(it) }
override fun atan(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { atan(it) }
override fun sinh(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { sinh(it) }
override fun cosh(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { cosh(it) }
override fun tanh(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { tanh(it) }
override fun asinh(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { asinh(it) }
override fun acosh(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { acosh(it) }
override fun atanh(arg: NDBuffer<Complex>): ComplexNDElement = map(arg) { atanh(it) }
}
/**
* Fast element production using function inlining
*/
public inline fun BufferedNDField<Complex, ComplexField>.produceInline(initializer: ComplexField.(Int) -> Complex): ComplexNDElement {
val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.initializer(offset) }
return BufferedNDFieldElement(this, buffer)
}
/**
* Map one [ComplexNDElement] using function with indices.
*/
public inline fun ComplexNDElement.mapIndexed(transform: ComplexField.(index: IntArray, Complex) -> Complex): ComplexNDElement =
context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) }
/**
* Map one [ComplexNDElement] using function without indices.
*/
public inline fun ComplexNDElement.map(transform: ComplexField.(Complex) -> Complex): ComplexNDElement {
val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.transform(buffer[offset]) }
return BufferedNDFieldElement(context, buffer)
}
/**
* Element by element application of any operation on elements to the whole array. Just like in numpy
*/
public operator fun Function1<Complex, Complex>.invoke(ndElement: ComplexNDElement): ComplexNDElement =
ndElement.map { this@invoke(it) }
/* plus and minus */
/**
* Summation operation for [BufferedNDElement] and single element
*/
public operator fun ComplexNDElement.plus(arg: Complex): ComplexNDElement = map { it + arg }
/**
* Subtraction operation between [BufferedNDElement] and single element
*/
public operator fun ComplexNDElement.minus(arg: Complex): ComplexNDElement = map { it - arg }
public operator fun ComplexNDElement.plus(arg: Double): ComplexNDElement = map { it + arg }
public operator fun ComplexNDElement.minus(arg: Double): ComplexNDElement = map { it - arg }
public fun NDField.Companion.complex(vararg shape: Int): ComplexNDField = ComplexNDField(shape)
public fun NDElement.Companion.complex(
vararg shape: Int,
initializer: ComplexField.(IntArray) -> Complex,
): ComplexNDElement = NDField.complex(*shape).produce(initializer)
/**
* Produce a context for n-dimensional operations inside this real field
*/
public inline fun <R> ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
return NDField.complex(*shape).action()
}

View File

@ -1,44 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.ExtendedField
/**
* [ExtendedField] over [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param F the extended field of structure elements.
*/
public interface ExtendedNDField<T : Any, F : ExtendedField<T>, N : NDStructure<T>> : NDField<T, F, N>, ExtendedField<N>
///**
// * NDField that supports [ExtendedField] operations on its elements
// */
//class ExtendedNDFieldWrapper<T : Any, F : ExtendedField<T>, N : NDStructure<T>>(private val ndField: NDField<T, F, N>) :
// ExtendedNDField<T, F, N>, NDField<T, F, N> by ndField {
//
// override val shape: IntArray get() = ndField.shape
// override val elementContext: F get() = ndField.elementContext
//
// override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer)
//
// override fun power(arg: N, pow: Double): N {
// return produce { with(elementContext) { power(arg[it], pow) } }
// }
//
// override fun exp(arg: N): N {
// return produce { with(elementContext) { exp(arg[it]) } }
// }
//
// override fun ln(arg: N): N {
// return produce { with(elementContext) { ln(arg[it]) } }
// }
//
// override fun sin(arg: N): N {
// return produce { with(elementContext) { sin(arg[it]) } }
// }
//
// override fun cos(arg: N): N {
// return produce { with(elementContext) { cos(arg[it]) } }
// }
//}

View File

@ -1,259 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.Complex
import kscience.kmath.operations.Field
import kscience.kmath.operations.Ring
import kscience.kmath.operations.Space
import kotlin.native.concurrent.ThreadLocal
/**
* An exception is thrown when the expected ans actual shape of NDArray differs.
*
* @property expected the expected shape.
* @property actual the actual shape.
*/
public class ShapeMismatchException(public val expected: IntArray, public val actual: IntArray) :
RuntimeException("Shape ${actual.contentToString()} doesn't fit in expected shape ${expected.contentToString()}.")
/**
* The base interface for all ND-algebra implementations.
*
* @param T the type of ND-structure element.
* @param C the type of the element context.
* @param N the type of the structure.
*/
public interface NDAlgebra<T, C, N : NDStructure<T>> {
/**
* The shape of ND-structures this algebra operates on.
*/
public val shape: IntArray
/**
* The algebra over elements of ND structure.
*/
public val elementContext: C
/**
* Produces a new [N] structure using given initializer function.
*/
public fun produce(initializer: C.(IntArray) -> T): N
/**
* Maps elements from one structure to another one by applying [transform] to them.
*/
public fun map(arg: N, transform: C.(T) -> T): N
/**
* Maps elements from one structure to another one by applying [transform] to them alongside with their indices.
*/
public fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N
/**
* Combines two structures into one.
*/
public fun combine(a: N, b: N, transform: C.(T, T) -> T): N
/**
* Checks if given element is consistent with this context.
*
* @param element the structure to check.
* @return the valid structure.
*/
public fun check(element: N): N {
if (!element.shape.contentEquals(shape)) throw ShapeMismatchException(shape, element.shape)
return element
}
/**
* Checks if given elements are consistent with this context.
*
* @param elements the structures to check.
* @return the array of valid structures.
*/
public fun check(vararg elements: N): Array<out N> = elements
.map(NDStructure<T>::shape)
.singleOrNull { !shape.contentEquals(it) }
?.let<IntArray, Array<out N>> { throw ShapeMismatchException(shape, it) }
?: elements
/**
* Element-wise invocation of function working on [T] on a [NDStructure].
*/
public operator fun Function1<T, T>.invoke(structure: N): N = map(structure) { value -> this@invoke(value) }
public companion object
}
/**
* Space of [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param S the type of space of structure elements.
*/
public interface NDSpace<T, S : Space<T>, N : NDStructure<T>> : Space<N>, NDAlgebra<T, S, N> {
/**
* Element-wise addition.
*
* @param a the addend.
* @param b the augend.
* @return the sum.
*/
public override fun add(a: N, b: N): N = combine(a, b) { aValue, bValue -> add(aValue, bValue) }
/**
* Element-wise multiplication by scalar.
*
* @param a the multiplicand.
* @param k the multiplier.
* @return the product.
*/
public override fun multiply(a: N, k: Number): N = map(a) { multiply(it, k) }
// TODO move to extensions after KEEP-176
/**
* Adds an ND structure to an element of it.
*
* @receiver the addend.
* @param arg the augend.
* @return the sum.
*/
public operator fun N.plus(arg: T): N = map(this) { value -> add(arg, value) }
/**
* Subtracts an element from ND structure of it.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun N.minus(arg: T): N = map(this) { value -> add(arg, -value) }
/**
* Adds an element to ND structure of it.
*
* @receiver the addend.
* @param arg the augend.
* @return the sum.
*/
public operator fun T.plus(arg: N): N = map(arg) { value -> add(this@plus, value) }
/**
* Subtracts an ND structure from an element of it.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun T.minus(arg: N): N = map(arg) { value -> add(-this@minus, value) }
public companion object
}
/**
* Ring of [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param R the type of ring of structure elements.
*/
public interface NDRing<T, R : Ring<T>, N : NDStructure<T>> : Ring<N>, NDSpace<T, R, N> {
/**
* Element-wise multiplication.
*
* @param a the multiplicand.
* @param b the multiplier.
* @return the product.
*/
public override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) }
//TODO move to extensions after KEEP-176
/**
* Multiplies an ND structure by an element of it.
*
* @receiver the multiplicand.
* @param arg the multiplier.
* @return the product.
*/
public operator fun N.times(arg: T): N = map(this) { value -> multiply(arg, value) }
/**
* Multiplies an element by a ND structure of it.
*
* @receiver the multiplicand.
* @param arg the multiplier.
* @return the product.
*/
public operator fun T.times(arg: N): N = map(arg) { value -> multiply(this@times, value) }
public companion object
}
/**
* Field of [NDStructure].
*
* @param T the type of the element contained in ND structure.
* @param N the type of ND structure.
* @param F the type field of structure elements.
*/
public interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F, N> {
/**
* Element-wise division.
*
* @param a the dividend.
* @param b the divisor.
* @return the quotient.
*/
public override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) }
//TODO move to extensions after KEEP-176
/**
* Divides an ND structure by an element of it.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun N.div(arg: T): N = map(this) { value -> divide(arg, value) }
/**
* Divides an element by an ND structure of it.
*
* @receiver the dividend.
* @param arg the divisor.
* @return the quotient.
*/
public operator fun T.div(arg: N): N = map(arg) { divide(it, this@div) }
@ThreadLocal
public companion object {
private val realNDFieldCache: MutableMap<IntArray, RealNDField> = hashMapOf()
/**
* Create a nd-field for [Double] values or pull it from cache if it was created previously.
*/
public fun real(vararg shape: Int): RealNDField = realNDFieldCache.getOrPut(shape) { RealNDField(shape) }
/**
* Create an ND field with boxing generic buffer.
*/
public fun <T : Any, F : Field<T>> boxing(
field: F,
vararg shape: Int,
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing
): BoxingNDField<T, F> = BoxingNDField(shape, field, bufferFactory)
/**
* Create a most suitable implementation for nd-field using reified class.
*/
@Suppress("UNCHECKED_CAST")
public inline fun <reified T : Any, F : Field<T>> auto(field: F, vararg shape: Int): BufferedNDField<T, F> =
when {
T::class == Double::class -> real(*shape) as BufferedNDField<T, F>
T::class == Complex::class -> complex(*shape) as BufferedNDField<T, F>
else -> BoxingNDField(shape, field, Buffer.Companion::auto)
}
}
}

View File

@ -1,134 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.operations.Field
import kscience.kmath.operations.RealField
import kscience.kmath.operations.Ring
import kscience.kmath.operations.Space
/**
* The root for all [NDStructure] based algebra elements. Does not implement algebra element root because of problems with recursive self-types
* @param T the type of the element of the structure
* @param C the type of the context for the element
* @param N the type of the underlying [NDStructure]
*/
public interface NDElement<T, C, N : NDStructure<T>> : NDStructure<T> {
public val context: NDAlgebra<T, C, N>
public fun unwrap(): N
public fun N.wrap(): NDElement<T, C, N>
public companion object {
/**
* Create a optimized NDArray of doubles
*/
public fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }): RealNDElement =
NDField.real(*shape).produce(initializer)
public inline fun real1D(dim: Int, crossinline initializer: (Int) -> Double = { _ -> 0.0 }): RealNDElement =
real(intArrayOf(dim)) { initializer(it[0]) }
public inline fun real2D(
dim1: Int,
dim2: Int,
crossinline initializer: (Int, Int) -> Double = { _, _ -> 0.0 }
): RealNDElement = real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) }
public inline fun real3D(
dim1: Int,
dim2: Int,
dim3: Int,
crossinline initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }
): RealNDElement = real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
/**
* Simple boxing NDArray
*/
public fun <T : Any, F : Field<T>> boxing(
shape: IntArray,
field: F,
initializer: F.(IntArray) -> T
): BufferedNDElement<T, F> {
val ndField = BoxingNDField(shape, field, Buffer.Companion::boxing)
return ndField.produce(initializer)
}
public inline fun <reified T : Any, F : Field<T>> auto(
shape: IntArray,
field: F,
noinline initializer: F.(IntArray) -> T
): BufferedNDFieldElement<T, F> {
val ndField = NDField.auto(field, *shape)
return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer)
}
}
}
public fun <T, C, N : NDStructure<T>> NDElement<T, C, N>.mapIndexed(transform: C.(index: IntArray, T) -> T): NDElement<T, C, N> =
context.mapIndexed(unwrap(), transform).wrap()
public fun <T, C, N : NDStructure<T>> NDElement<T, C, N>.map(transform: C.(T) -> T): NDElement<T, C, N> =
context.map(unwrap(), transform).wrap()
/**
* Element by element application of any operation on elements to the whole [NDElement]
*/
public operator fun <T, C, N : NDStructure<T>> Function1<T, T>.invoke(ndElement: NDElement<T, C, N>): NDElement<T, C, N> =
ndElement.map { value -> this@invoke(value) }
/* plus and minus */
/**
* Summation operation for [NDElement] and single element
*/
public operator fun <T, S : Space<T>, N : NDStructure<T>> NDElement<T, S, N>.plus(arg: T): NDElement<T, S, N> =
map { value -> arg + value }
/**
* Subtraction operation between [NDElement] and single element
*/
public operator fun <T, S : Space<T>, N : NDStructure<T>> NDElement<T, S, N>.minus(arg: T): NDElement<T, S, N> =
map { value -> arg - value }
/* prod and div */
/**
* Product operation for [NDElement] and single element
*/
public operator fun <T, R : Ring<T>, N : NDStructure<T>> NDElement<T, R, N>.times(arg: T): NDElement<T, R, N> =
map { value -> arg * value }
/**
* Division operation between [NDElement] and single element
*/
public operator fun <T, F : Field<T>, N : NDStructure<T>> NDElement<T, F, N>.div(arg: T): NDElement<T, F, N> =
map { value -> arg / value }
// /**
// * Reverse sum operation
// */
// operator fun T.plus(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
// field.run { this@plus + arg[index] }
// }
//
// /**
// * Reverse minus operation
// */
// operator fun T.minus(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
// field.run { this@minus - arg[index] }
// }
//
// /**
// * Reverse product operation
// */
// operator fun T.times(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
// field.run { this@times * arg[index] }
// }
//
// /**
// * Reverse division operation
// */
// operator fun T.div(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
// field.run { this@div / arg[index] }
// }

View File

@ -1,140 +0,0 @@
package kscience.kmath.structures
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.FieldElement
import kscience.kmath.operations.RealField
import kscience.kmath.operations.RingWithNumbers
public typealias RealNDElement = BufferedNDFieldElement<Double, RealField>
@OptIn(UnstableKMathAPI::class)
public class RealNDField(override val shape: IntArray) :
BufferedNDField<Double, RealField>,
ExtendedNDField<Double, RealField, NDBuffer<Double>>,
RingWithNumbers<NDBuffer<Double>> {
override val strides: Strides = DefaultStrides(shape)
override val elementContext: RealField get() = RealField
override val zero: RealNDElement by lazy { produce { zero } }
override val one: RealNDElement by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Double> {
val d = value.toDouble()
return produce { d }
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun map(
arg: NDBuffer<Double>,
transform: RealField.(Double) -> Double,
): RealNDElement {
check(arg)
val array = RealBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
return BufferedNDFieldElement(this, array)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
val array = RealBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
return BufferedNDFieldElement(this, array)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun mapIndexed(
arg: NDBuffer<Double>,
transform: RealField.(index: IntArray, Double) -> Double,
): RealNDElement {
check(arg)
return BufferedNDFieldElement(
this,
RealBuffer(arg.strides.linearSize) { offset ->
elementContext.transform(
arg.strides.index(offset),
arg.buffer[offset]
)
})
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun combine(
a: NDBuffer<Double>,
b: NDBuffer<Double>,
transform: RealField.(Double, Double) -> Double,
): RealNDElement {
check(a, b)
val buffer = RealBuffer(strides.linearSize) { offset ->
elementContext.transform(a.buffer[offset], b.buffer[offset])
}
return BufferedNDFieldElement(this, buffer)
}
override fun NDBuffer<Double>.toElement(): FieldElement<NDBuffer<Double>, *, out BufferedNDField<Double, RealField>> =
BufferedNDFieldElement(this@RealNDField, buffer)
override fun power(arg: NDBuffer<Double>, pow: Number): RealNDElement = map(arg) { power(it, pow) }
override fun exp(arg: NDBuffer<Double>): RealNDElement = map(arg) { exp(it) }
override fun ln(arg: NDBuffer<Double>): RealNDElement = map(arg) { ln(it) }
override fun sin(arg: NDBuffer<Double>): RealNDElement = map(arg) { sin(it) }
override fun cos(arg: NDBuffer<Double>): RealNDElement = map(arg) { cos(it) }
override fun tan(arg: NDBuffer<Double>): RealNDElement = map(arg) { tan(it) }
override fun asin(arg: NDBuffer<Double>): RealNDElement = map(arg) { asin(it) }
override fun acos(arg: NDBuffer<Double>): RealNDElement = map(arg) { acos(it) }
override fun atan(arg: NDBuffer<Double>): RealNDElement = map(arg) { atan(it) }
override fun sinh(arg: NDBuffer<Double>): RealNDElement = map(arg) { sinh(it) }
override fun cosh(arg: NDBuffer<Double>): RealNDElement = map(arg) { cosh(it) }
override fun tanh(arg: NDBuffer<Double>): RealNDElement = map(arg) { tanh(it) }
override fun asinh(arg: NDBuffer<Double>): RealNDElement = map(arg) { asinh(it) }
override fun acosh(arg: NDBuffer<Double>): RealNDElement = map(arg) { acosh(it) }
override fun atanh(arg: NDBuffer<Double>): RealNDElement = map(arg) { atanh(it) }
}
/**
* Fast element production using function inlining
*/
public inline fun BufferedNDField<Double, RealField>.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement {
val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) }
return BufferedNDFieldElement(this, RealBuffer(array))
}
/**
* Map one [RealNDElement] using function with indices.
*/
public inline fun RealNDElement.mapIndexed(crossinline transform: RealField.(index: IntArray, Double) -> Double): RealNDElement =
context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) }
/**
* Map one [RealNDElement] using function without indices.
*/
public inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> Double): RealNDElement {
val array = DoubleArray(strides.linearSize) { offset -> RealField.transform(buffer[offset]) }
return BufferedNDFieldElement(context, RealBuffer(array))
}
/**
* Element by element application of any operation on elements to the whole array. Just like in numpy.
*/
public operator fun Function1<Double, Double>.invoke(ndElement: RealNDElement): RealNDElement =
ndElement.map { this@invoke(it) }
/* plus and minus */
/**
* Summation operation for [BufferedNDElement] and single element
*/
public operator fun RealNDElement.plus(arg: Double): RealNDElement = map { it + arg }
/**
* Subtraction operation between [BufferedNDElement] and single element
*/
public operator fun RealNDElement.minus(arg: Double): RealNDElement = map { it - arg }
/**
* Produce a context for n-dimensional operations inside this real field
*/
public inline fun <R> RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R = NDField.real(*shape).run(action)

View File

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

View File

@ -1,9 +1,8 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.nd.NDStructure
import kscience.kmath.nd.as2D
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.NDStructure
import kscience.kmath.structures.as2D
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -1,6 +1,5 @@
package kscience.kmath.linear package kscience.kmath.linear
import kscience.kmath.structures.Matrix
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -1,5 +1,8 @@
package kscience.kmath.structures package kscience.kmath.structures
import kscience.kmath.nd.NDAlgebra
import kscience.kmath.nd.get
import kscience.kmath.nd.real
import kscience.kmath.operations.internal.FieldVerifier import kscience.kmath.operations.internal.FieldVerifier
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -7,12 +10,12 @@ import kotlin.test.assertEquals
internal class NDFieldTest { internal class NDFieldTest {
@Test @Test
fun verify() { fun verify() {
NDField.real(12, 32).run { FieldVerifier(this, one + 3, one - 23, one * 12, 6.66) } NDAlgebra.real(12, 32).run { FieldVerifier(this, one + 3, one - 23, one * 12, 6.66) }
} }
@Test @Test
fun testStrides() { fun testStrides() {
val ndArray = NDElement.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() } val ndArray = NDAlgebra.real(10, 10).produce { (it[0] + it[1]).toDouble() }
assertEquals(ndArray[5, 5], 10.0) assertEquals(ndArray[5, 5], 10.0)
} }
} }

View File

@ -1,8 +1,8 @@
package kscience.kmath.structures package kscience.kmath.structures
import kscience.kmath.nd.*
import kscience.kmath.operations.Norm import kscience.kmath.operations.Norm
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.NDElement.Companion.real2D
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.pow import kotlin.math.pow
import kotlin.test.Test import kotlin.test.Test
@ -10,25 +10,30 @@ import kotlin.test.assertEquals
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
class NumberNDFieldTest { class NumberNDFieldTest {
val array1: RealNDElement = real2D(3, 3) { i, j -> (i + j).toDouble() } val algebra = NDAlgebra.real(3,3)
val array2: RealNDElement = real2D(3, 3) { i, j -> (i - j).toDouble() } val array1 = algebra.produce { (i, j) -> (i + j).toDouble() }
val array2 = algebra.produce { (i, j) -> (i - j).toDouble() }
@Test @Test
fun testSum() { fun testSum() {
algebra {
val sum = array1 + array2 val sum = array1 + array2
assertEquals(4.0, sum[2, 2]) assertEquals(4.0, sum[2, 2])
} }
}
@Test @Test
fun testProduct() { fun testProduct() {
algebra {
val product = array1 * array2 val product = array1 * array2
assertEquals(0.0, product[2, 2]) assertEquals(0.0, product[2, 2])
} }
}
@Test @Test
fun testGeneration() { fun testGeneration() {
val array = real2D(3, 3) { i, j -> (i * 10 + j).toDouble() } val array = Structure2D.real(3, 3) { i, j -> (i * 10 + j).toDouble() }
for (i in 0..2) { for (i in 0..2) {
for (j in 0..2) { for (j in 0..2) {
@ -40,17 +45,21 @@ class NumberNDFieldTest {
@Test @Test
fun testExternalFunction() { fun testExternalFunction() {
algebra {
val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 } val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 }
val result = function(array1) + 1.0 val result = function(array1) + 1.0
assertEquals(10.0, result[1, 1]) assertEquals(10.0, result[1, 1])
} }
}
@Test @Test
fun testLibraryFunction() { fun testLibraryFunction() {
algebra {
val abs: (Double) -> Double = ::abs val abs: (Double) -> Double = ::abs
val result = abs(array2) val result = abs(array2)
assertEquals(2.0, result[0, 2]) assertEquals(2.0, result[0, 2])
} }
}
@Test @Test
fun combineTest() { fun combineTest() {
@ -64,6 +73,8 @@ class NumberNDFieldTest {
@Test @Test
fun testInternalContext() { fun testInternalContext() {
(NDField.real(*array1.shape)) { with(L2Norm) { 1 + norm(array1) + exp(array2) } } algebra {
(NDAlgebra.real(*array1.shape)) { with(L2Norm) { 1 + norm(array1) + exp(array2) } }
}
} }
} }

View File

@ -2,6 +2,8 @@ package kscience.kmath.structures
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kscience.kmath.coroutines.Math import kscience.kmath.coroutines.Math
import kscience.kmath.nd.DefaultStrides
import kscience.kmath.nd.NDStructure
public class LazyNDStructure<T>( public class LazyNDStructure<T>(
public val scope: CoroutineScope, public val scope: CoroutineScope,

View File

@ -1,9 +1,8 @@
package kscience.kmath.dimensions package kscience.kmath.dimensions
import kscience.kmath.linear.* import kscience.kmath.linear.*
import kscience.kmath.nd.Structure2D
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.Structure2D
/** /**
* A matrix with compile-time controlled dimension * A matrix with compile-time controlled dimension

View File

@ -2,8 +2,7 @@ package kscience.kmath.ejml
import kscience.kmath.linear.* import kscience.kmath.linear.*
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Matrix import kscience.kmath.nd.NDStructure
import kscience.kmath.structures.NDStructure
import kscience.kmath.structures.RealBuffer import kscience.kmath.structures.RealBuffer
import org.ejml.dense.row.factory.DecompositionFactory_DDRM import org.ejml.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix import org.ejml.simple.SimpleMatrix

View File

@ -1,12 +1,8 @@
package kscience.kmath.ejml package kscience.kmath.ejml
import kscience.kmath.linear.InverseMatrixFeature import kscience.kmath.linear.*
import kscience.kmath.linear.MatrixContext
import kscience.kmath.linear.Point
import kscience.kmath.linear.origin
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Matrix import kscience.kmath.nd.getFeature
import kscience.kmath.structures.getFeature
import org.ejml.simple.SimpleMatrix import org.ejml.simple.SimpleMatrix
/** /**

View File

@ -5,7 +5,7 @@ import kscience.kmath.linear.LupDecompositionFeature
import kscience.kmath.linear.MatrixFeature import kscience.kmath.linear.MatrixFeature
import kscience.kmath.linear.plus import kscience.kmath.linear.plus
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.getFeature import kscience.kmath.nd.getFeature
import org.ejml.dense.row.factory.DecompositionFactory_DDRM import org.ejml.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix import org.ejml.simple.SimpleMatrix
import kotlin.random.Random import kotlin.random.Random

View File

@ -1,12 +1,8 @@
package kscience.kmath.real package kscience.kmath.real
import kscience.kmath.linear.MatrixContext import kscience.kmath.linear.*
import kscience.kmath.linear.VirtualMatrix
import kscience.kmath.linear.inverseWithLup
import kscience.kmath.linear.real
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Buffer import kscience.kmath.structures.Buffer
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.RealBuffer import kscience.kmath.structures.RealBuffer
import kscience.kmath.structures.asIterable import kscience.kmath.structures.asIterable
import kotlin.math.pow import kotlin.math.pow
@ -144,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.max(): Double? = elements().map { (_, value) -> value }.maxOrNull()
public fun RealMatrix.average(): Double = elements().map { (_, value) -> value }.average() 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 -> MatrixContext.real.produce(rowNum, colNum) { i, j ->
transform(get(i, j)) transform(get(i, j))
} }

View File

@ -0,0 +1,31 @@
package kscience.kmath.real
import kscience.kmath.nd.NDBuffer
import kscience.kmath.operations.RealField
import kscience.kmath.structures.RealBuffer
/**
* Map one [NDBuffer] using function without indices.
*/
public inline fun NDBuffer<Double>.mapInline(crossinline transform: RealField.(Double) -> Double): NDBuffer<Double> {
val array = DoubleArray(strides.linearSize) { offset -> RealField.transform(buffer[offset]) }
return NDBuffer(strides, RealBuffer(array))
}
/**
* Element by element application of any operation on elements to the whole array. Just like in numpy.
*/
public operator fun Function1<Double, Double>.invoke(ndElement: NDBuffer<Double>): NDBuffer<Double> =
ndElement.mapInline { this@invoke(it) }
/* plus and minus */
/**
* Summation operation for [NDBuffer] and single element
*/
public operator fun NDBuffer<Double>.plus(arg: Double): NDBuffer<Double> = mapInline { it + arg }
/**
* Subtraction operation between [NDBuffer] and single element
*/
public operator fun NDBuffer<Double>.minus(arg: Double): NDBuffer<Double> = mapInline { it - arg }

View File

@ -1,8 +1,8 @@
package kaceince.kmath.real package kaceince.kmath.real
import kscience.kmath.linear.Matrix
import kscience.kmath.linear.build import kscience.kmath.linear.build
import kscience.kmath.real.* import kscience.kmath.real.*
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.contentEquals import kscience.kmath.structures.contentEquals
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -1,7 +1,7 @@
package kscience.kmath.interpolation package kscience.kmath.interpolation
import kscience.kmath.nd.Structure2D
import kscience.kmath.structures.Buffer import kscience.kmath.structures.Buffer
import kscience.kmath.structures.Structure2D
public interface XYPointSet<X, Y> { public interface XYPointSet<X, Y> {
public val size: Int public val size: Int

View File

@ -1,8 +1,18 @@
plugins { id("ru.mipt.npm.mpp") } plugins { id("ru.mipt.npm.mpp") }
kotlin.sourceSets.commonMain { kotlin.sourceSets {
commonMain {
dependencies { dependencies {
api(project(":kmath-core")) api(project(":kmath-core"))
api(project(":kmath-for-real")) }
}
commonTest{
dependencies{
implementation(project(":kmath-for-real"))
}
} }
} }
readme {
this.maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE
}

View File

@ -1,6 +1,8 @@
package kscience.kmath.histogram package kscience.kmath.histogram
import kscience.kmath.linear.Point import kscience.kmath.linear.Point
import kscience.kmath.nd.DefaultStrides
import kscience.kmath.nd.NDStructure
import kscience.kmath.operations.SpaceOperations import kscience.kmath.operations.SpaceOperations
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import kscience.kmath.structures.* import kscience.kmath.structures.*

View File

@ -5,7 +5,6 @@ import kscience.kmath.histogram.fill
import kscience.kmath.histogram.put import kscience.kmath.histogram.put
import kscience.kmath.real.RealVector import kscience.kmath.real.RealVector
import kscience.kmath.real.invoke import kscience.kmath.real.invoke
import kscience.kmath.structures.Buffer
import kotlin.random.Random import kotlin.random.Random
import kotlin.test.* import kotlin.test.*

View File

@ -1,6 +1,6 @@
package kscience.kmath.histogram package kscience.kmath.histogram
import kscience.kmath.real.RealVector import kscience.kmath.linear.Point
import kscience.kmath.structures.Buffer import kscience.kmath.structures.Buffer
import kscience.kmath.structures.asBuffer import kscience.kmath.structures.asBuffer
import java.util.* import java.util.*
@ -11,12 +11,12 @@ import kotlin.math.floor
public class UnivariateBin( public class UnivariateBin(
public val position: Double, public val position: Double,
public val size: Double, public val size: Double,
public val counter: LongCounter = LongCounter() public val counter: LongCounter = LongCounter(),
) : Bin<Double> { ) : Bin<Double> {
//TODO add weighting //TODO add weighting
public override val value: Number get() = counter.sum() public override val value: Number get() = counter.sum()
public override val center: RealVector get() = doubleArrayOf(position).asBuffer() public override val center: Point<Double> get() = doubleArrayOf(position).asBuffer()
public override val dimension: Int get() = 1 public override val dimension: Int get() = 1
public operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2) public operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2)
@ -27,8 +27,9 @@ public class UnivariateBin(
/** /**
* Univariate histogram with log(n) bin search speed * Univariate histogram with log(n) bin search speed
*/ */
public class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : public class UnivariateHistogram private constructor(
MutableHistogram<Double, UnivariateBin> { private val factory: (Double) -> UnivariateBin,
) : MutableHistogram<Double, UnivariateBin> {
private val bins: TreeMap<Double, UnivariateBin> = TreeMap() private val bins: TreeMap<Double, UnivariateBin> = TreeMap()

View File

@ -1,55 +1,68 @@
package kscience.kmath.nd4j package kscience.kmath.nd4j
import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.nd.*
import kscience.kmath.operations.* import kscience.kmath.operations.*
import kscience.kmath.structures.NDAlgebra import kscience.kmath.structures.*
import kscience.kmath.structures.NDField
import kscience.kmath.structures.NDRing
import kscience.kmath.structures.NDSpace
import org.nd4j.linalg.api.ndarray.INDArray import org.nd4j.linalg.api.ndarray.INDArray
import org.nd4j.linalg.factory.Nd4j import org.nd4j.linalg.factory.Nd4j
internal fun NDAlgebra<*, *>.checkShape(array: INDArray): INDArray {
val arrayShape = array.shape().toIntArray()
if (!shape.contentEquals(arrayShape)) throw ShapeMismatchException(shape, arrayShape)
return array
}
/** /**
* Represents [NDAlgebra] over [Nd4jArrayAlgebra]. * Represents [NDAlgebra] over [Nd4jArrayAlgebra].
* *
* @param T the type of ND-structure element. * @param T the type of ND-structure element.
* @param C the type of the element context. * @param C the type of the element context.
*/ */
public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C, Nd4jArrayStructure<T>> { public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C> {
/** /**
* Wraps [INDArray] to [N]. * Wraps [INDArray] to [N].
*/ */
public fun INDArray.wrap(): Nd4jArrayStructure<T> public fun INDArray.wrap(): Nd4jArrayStructure<T>
public val NDStructure<T>.ndArray: INDArray
get() = when {
!shape.contentEquals(this@Nd4jArrayAlgebra.shape) -> throw ShapeMismatchException(
this@Nd4jArrayAlgebra.shape,
shape
)
this is Nd4jArrayStructure -> ndArray //TODO check strides
else -> {
TODO()
}
}
public override fun produce(initializer: C.(IntArray) -> T): Nd4jArrayStructure<T> { public override fun produce(initializer: C.(IntArray) -> T): Nd4jArrayStructure<T> {
val struct = Nd4j.create(*shape)!!.wrap() val struct = Nd4j.create(*shape)!!.wrap()
struct.indicesIterator().forEach { struct[it] = elementContext.initializer(it) } struct.indicesIterator().forEach { struct[it] = elementContext.initializer(it) }
return struct return struct
} }
public override fun map(arg: Nd4jArrayStructure<T>, transform: C.(T) -> T): Nd4jArrayStructure<T> { public override fun NDStructure<T>.map(transform: C.(T) -> T): Nd4jArrayStructure<T> {
check(arg) val newStruct = ndArray.dup().wrap()
val newStruct = arg.ndArray.dup().wrap()
newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) } newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) }
return newStruct return newStruct
} }
public override fun mapIndexed( public override fun NDStructure<T>.mapIndexed(
arg: Nd4jArrayStructure<T>,
transform: C.(index: IntArray, T) -> T, transform: C.(index: IntArray, T) -> T,
): Nd4jArrayStructure<T> { ): Nd4jArrayStructure<T> {
check(arg) val new = Nd4j.create(*this@Nd4jArrayAlgebra.shape).wrap()
val new = Nd4j.create(*shape).wrap() new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, this[idx]) }
new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, arg[idx]) }
return new return new
} }
public override fun combine( public override fun combine(
a: Nd4jArrayStructure<T>, a: NDStructure<T>,
b: Nd4jArrayStructure<T>, b: NDStructure<T>,
transform: C.(T, T) -> T, transform: C.(T, T) -> T,
): Nd4jArrayStructure<T> { ): Nd4jArrayStructure<T> {
check(a, b)
val new = Nd4j.create(*shape).wrap() val new = Nd4j.create(*shape).wrap()
new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(a[idx], b[idx]) } new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(a[idx], b[idx]) }
return new return new
@ -62,38 +75,32 @@ public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C, Nd4jArrayStructure<T>>
* @param T the type of the element contained in ND structure. * @param T the type of the element contained in ND structure.
* @param S the type of space of structure elements. * @param S the type of space of structure elements.
*/ */
public interface Nd4jArraySpace<T, S : Space<T>> : NDSpace<T, S, Nd4jArrayStructure<T>>, Nd4jArrayAlgebra<T, S> { public interface Nd4jArraySpace<T, S : Space<T>> : NDSpace<T, S>, Nd4jArrayAlgebra<T, S> {
public override val zero: Nd4jArrayStructure<T> public override val zero: Nd4jArrayStructure<T>
get() = Nd4j.zeros(*shape).wrap() get() = Nd4j.zeros(*shape).wrap()
public override fun add(a: Nd4jArrayStructure<T>, b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> { public override fun add(a: NDStructure<T>, b: NDStructure<T>): Nd4jArrayStructure<T> {
check(a, b)
return a.ndArray.add(b.ndArray).wrap() return a.ndArray.add(b.ndArray).wrap()
} }
public override operator fun Nd4jArrayStructure<T>.minus(b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> { public override operator fun NDStructure<T>.minus(b: NDStructure<T>): Nd4jArrayStructure<T> {
check(this, b)
return ndArray.sub(b.ndArray).wrap() return ndArray.sub(b.ndArray).wrap()
} }
public override operator fun Nd4jArrayStructure<T>.unaryMinus(): Nd4jArrayStructure<T> { public override operator fun NDStructure<T>.unaryMinus(): Nd4jArrayStructure<T> {
check(this)
return ndArray.neg().wrap() return ndArray.neg().wrap()
} }
public override fun multiply(a: Nd4jArrayStructure<T>, k: Number): Nd4jArrayStructure<T> { public override fun multiply(a: NDStructure<T>, k: Number): Nd4jArrayStructure<T> {
check(a)
return a.ndArray.mul(k).wrap() return a.ndArray.mul(k).wrap()
} }
public override operator fun Nd4jArrayStructure<T>.div(k: Number): Nd4jArrayStructure<T> { public override operator fun NDStructure<T>.div(k: Number): Nd4jArrayStructure<T> {
check(this)
return ndArray.div(k).wrap() return ndArray.div(k).wrap()
} }
public override operator fun Nd4jArrayStructure<T>.times(k: Number): Nd4jArrayStructure<T> { public override operator fun NDStructure<T>.times(k: Number): Nd4jArrayStructure<T> {
check(this)
return ndArray.mul(k).wrap() return ndArray.mul(k).wrap()
} }
} }
@ -105,13 +112,12 @@ public interface Nd4jArraySpace<T, S : Space<T>> : NDSpace<T, S, Nd4jArrayStruct
* @param R the type of ring of structure elements. * @param R the type of ring of structure elements.
*/ */
@OptIn(UnstableKMathAPI::class) @OptIn(UnstableKMathAPI::class)
public interface Nd4jArrayRing<T, R : Ring<T>> : NDRing<T, R, Nd4jArrayStructure<T>>, Nd4jArraySpace<T, R> { public interface Nd4jArrayRing<T, R : Ring<T>> : NDRing<T, R>, Nd4jArraySpace<T, R> {
public override val one: Nd4jArrayStructure<T> public override val one: Nd4jArrayStructure<T>
get() = Nd4j.ones(*shape).wrap() get() = Nd4j.ones(*shape).wrap()
public override fun multiply(a: Nd4jArrayStructure<T>, b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> { public override fun multiply(a: NDStructure<T>, b: NDStructure<T>): Nd4jArrayStructure<T> {
check(a, b)
return a.ndArray.mul(b.ndArray).wrap() return a.ndArray.mul(b.ndArray).wrap()
} }
// //
@ -168,17 +174,12 @@ public interface Nd4jArrayRing<T, R : Ring<T>> : NDRing<T, R, Nd4jArrayStructure
* @param N the type of ND structure. * @param N the type of ND structure.
* @param F the type field of structure elements. * @param F the type field of structure elements.
*/ */
public interface Nd4jArrayField<T, F : Field<T>> : NDField<T, F, Nd4jArrayStructure<T>>, Nd4jArrayRing<T, F> { public interface Nd4jArrayField<T, F : Field<T>> : NDField<T, F>, Nd4jArrayRing<T, F> {
public override fun divide(a: Nd4jArrayStructure<T>, b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> { public override fun divide(a: NDStructure<T>, b: NDStructure<T>): Nd4jArrayStructure<T> =
check(a, b) a.ndArray.div(b.ndArray).wrap()
return a.ndArray.div(b.ndArray).wrap()
}
public override operator fun Number.div(b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> { public override operator fun Number.div(b: NDStructure<T>): Nd4jArrayStructure<T> = b.ndArray.rdiv(this).wrap()
check(b)
return b.ndArray.rdiv(this).wrap()
}
public companion object { public companion object {
@ -219,35 +220,29 @@ public class RealNd4jArrayField(public override val shape: IntArray) : Nd4jArray
public override val elementContext: RealField public override val elementContext: RealField
get() = RealField get() = RealField
public override fun INDArray.wrap(): Nd4jArrayStructure<Double> = check(asRealStructure()) public override fun INDArray.wrap(): Nd4jArrayStructure<Double> = checkShape(this).asRealStructure()
public override operator fun Nd4jArrayStructure<Double>.div(arg: Double): Nd4jArrayStructure<Double> { public override operator fun NDStructure<Double>.div(arg: Double): Nd4jArrayStructure<Double> {
check(this)
return ndArray.div(arg).wrap() return ndArray.div(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Double>.plus(arg: Double): Nd4jArrayStructure<Double> { public override operator fun NDStructure<Double>.plus(arg: Double): Nd4jArrayStructure<Double> {
check(this)
return ndArray.add(arg).wrap() return ndArray.add(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Double>.minus(arg: Double): Nd4jArrayStructure<Double> { public override operator fun NDStructure<Double>.minus(arg: Double): Nd4jArrayStructure<Double> {
check(this)
return ndArray.sub(arg).wrap() return ndArray.sub(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Double>.times(arg: Double): Nd4jArrayStructure<Double> { public override operator fun NDStructure<Double>.times(arg: Double): Nd4jArrayStructure<Double> {
check(this)
return ndArray.mul(arg).wrap() return ndArray.mul(arg).wrap()
} }
public override operator fun Double.div(arg: Nd4jArrayStructure<Double>): Nd4jArrayStructure<Double> { public override operator fun Double.div(arg: NDStructure<Double>): Nd4jArrayStructure<Double> {
check(arg)
return arg.ndArray.rdiv(this).wrap() return arg.ndArray.rdiv(this).wrap()
} }
public override operator fun Double.minus(arg: Nd4jArrayStructure<Double>): Nd4jArrayStructure<Double> { public override operator fun Double.minus(arg: NDStructure<Double>): Nd4jArrayStructure<Double> {
check(arg)
return arg.ndArray.rsub(this).wrap() return arg.ndArray.rsub(this).wrap()
} }
} }
@ -259,35 +254,29 @@ public class FloatNd4jArrayField(public override val shape: IntArray) : Nd4jArra
public override val elementContext: FloatField public override val elementContext: FloatField
get() = FloatField get() = FloatField
public override fun INDArray.wrap(): Nd4jArrayStructure<Float> = check(asFloatStructure()) public override fun INDArray.wrap(): Nd4jArrayStructure<Float> = checkShape(this).asFloatStructure()
public override operator fun Nd4jArrayStructure<Float>.div(arg: Float): Nd4jArrayStructure<Float> { public override operator fun NDStructure<Float>.div(arg: Float): Nd4jArrayStructure<Float> {
check(this)
return ndArray.div(arg).wrap() return ndArray.div(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Float>.plus(arg: Float): Nd4jArrayStructure<Float> { public override operator fun NDStructure<Float>.plus(arg: Float): Nd4jArrayStructure<Float> {
check(this)
return ndArray.add(arg).wrap() return ndArray.add(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Float>.minus(arg: Float): Nd4jArrayStructure<Float> { public override operator fun NDStructure<Float>.minus(arg: Float): Nd4jArrayStructure<Float> {
check(this)
return ndArray.sub(arg).wrap() return ndArray.sub(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Float>.times(arg: Float): Nd4jArrayStructure<Float> { public override operator fun NDStructure<Float>.times(arg: Float): Nd4jArrayStructure<Float> {
check(this)
return ndArray.mul(arg).wrap() return ndArray.mul(arg).wrap()
} }
public override operator fun Float.div(arg: Nd4jArrayStructure<Float>): Nd4jArrayStructure<Float> { public override operator fun Float.div(arg: NDStructure<Float>): Nd4jArrayStructure<Float> {
check(arg)
return arg.ndArray.rdiv(this).wrap() return arg.ndArray.rdiv(this).wrap()
} }
public override operator fun Float.minus(arg: Nd4jArrayStructure<Float>): Nd4jArrayStructure<Float> { public override operator fun Float.minus(arg: NDStructure<Float>): Nd4jArrayStructure<Float> {
check(arg)
return arg.ndArray.rsub(this).wrap() return arg.ndArray.rsub(this).wrap()
} }
} }
@ -299,25 +288,21 @@ public class IntNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRi
public override val elementContext: IntRing public override val elementContext: IntRing
get() = IntRing get() = IntRing
public override fun INDArray.wrap(): Nd4jArrayStructure<Int> = check(asIntStructure()) public override fun INDArray.wrap(): Nd4jArrayStructure<Int> = checkShape(this).asIntStructure()
public override operator fun Nd4jArrayStructure<Int>.plus(arg: Int): Nd4jArrayStructure<Int> { public override operator fun NDStructure<Int>.plus(arg: Int): Nd4jArrayStructure<Int> {
check(this)
return ndArray.add(arg).wrap() return ndArray.add(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Int>.minus(arg: Int): Nd4jArrayStructure<Int> { public override operator fun NDStructure<Int>.minus(arg: Int): Nd4jArrayStructure<Int> {
check(this)
return ndArray.sub(arg).wrap() return ndArray.sub(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Int>.times(arg: Int): Nd4jArrayStructure<Int> { public override operator fun NDStructure<Int>.times(arg: Int): Nd4jArrayStructure<Int> {
check(this)
return ndArray.mul(arg).wrap() return ndArray.mul(arg).wrap()
} }
public override operator fun Int.minus(arg: Nd4jArrayStructure<Int>): Nd4jArrayStructure<Int> { public override operator fun Int.minus(arg: NDStructure<Int>): Nd4jArrayStructure<Int> {
check(arg)
return arg.ndArray.rsub(this).wrap() return arg.ndArray.rsub(this).wrap()
} }
} }
@ -329,25 +314,21 @@ public class LongNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayR
public override val elementContext: LongRing public override val elementContext: LongRing
get() = LongRing get() = LongRing
public override fun INDArray.wrap(): Nd4jArrayStructure<Long> = check(asLongStructure()) public override fun INDArray.wrap(): Nd4jArrayStructure<Long> = checkShape(this).asLongStructure()
public override operator fun Nd4jArrayStructure<Long>.plus(arg: Long): Nd4jArrayStructure<Long> { public override operator fun NDStructure<Long>.plus(arg: Long): Nd4jArrayStructure<Long> {
check(this)
return ndArray.add(arg).wrap() return ndArray.add(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Long>.minus(arg: Long): Nd4jArrayStructure<Long> { public override operator fun NDStructure<Long>.minus(arg: Long): Nd4jArrayStructure<Long> {
check(this)
return ndArray.sub(arg).wrap() return ndArray.sub(arg).wrap()
} }
public override operator fun Nd4jArrayStructure<Long>.times(arg: Long): Nd4jArrayStructure<Long> { public override operator fun NDStructure<Long>.times(arg: Long): Nd4jArrayStructure<Long> {
check(this)
return ndArray.mul(arg).wrap() return ndArray.mul(arg).wrap()
} }
public override operator fun Long.minus(arg: Nd4jArrayStructure<Long>): Nd4jArrayStructure<Long> { public override operator fun Long.minus(arg: NDStructure<Long>): Nd4jArrayStructure<Long> {
check(arg)
return arg.ndArray.rsub(this).wrap() return arg.ndArray.rsub(this).wrap()
} }
} }

View File

@ -1,7 +1,7 @@
package kscience.kmath.nd4j package kscience.kmath.nd4j
import kscience.kmath.structures.MutableNDStructure import kscience.kmath.nd.MutableNDStructure
import kscience.kmath.structures.NDStructure import kscience.kmath.nd.NDStructure
import org.nd4j.linalg.api.ndarray.INDArray import org.nd4j.linalg.api.ndarray.INDArray
/** /**

View File

@ -1,7 +1,7 @@
package kscience.kmath.nd4j package kscience.kmath.nd4j
import org.nd4j.linalg.factory.Nd4j
import kscience.kmath.operations.invoke import kscience.kmath.operations.invoke
import org.nd4j.linalg.factory.Nd4j
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.fail import kotlin.test.fail
@ -20,7 +20,7 @@ internal class Nd4jArrayAlgebraTest {
@Test @Test
fun testMap() { 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() val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure()
expected[intArrayOf(0, 0)] = 3 expected[intArrayOf(0, 0)] = 3
expected[intArrayOf(0, 1)] = 3 expected[intArrayOf(0, 1)] = 3

View File

@ -1,6 +1,6 @@
package kscience.kmath.nd4j package kscience.kmath.nd4j
import kscience.kmath.structures.get import kscience.kmath.nd.get
import org.nd4j.linalg.factory.Nd4j import org.nd4j.linalg.factory.Nd4j
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -1,10 +1,10 @@
package kscience.kmath.viktor 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.RealField
import kscience.kmath.structures.DefaultStrides import kscience.kmath.operations.RingWithNumbers
import kscience.kmath.structures.MutableNDStructure
import kscience.kmath.structures.NDField
import kscience.kmath.structures.Strides
import org.jetbrains.bio.viktor.F64Array import org.jetbrains.bio.viktor.F64Array
@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
@ -23,15 +23,28 @@ public inline class ViktorNDStructure(public val f64Buffer: F64Array) : MutableN
public fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this) public fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this)
@OptIn(UnstableKMathAPI::class)
@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
public class ViktorNDField(public override val shape: IntArray) : NDField<Double, RealField, ViktorNDStructure> { 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 {
!shape.contentEquals(this@ViktorNDField.shape) -> throw ShapeMismatchException(
this@ViktorNDField.shape,
shape
)
this is ViktorNDStructure && this.f64Buffer.shape.contentEquals(this@ViktorNDField.shape) -> this.f64Buffer
else -> produce { this@f64Buffer[it] }.f64Buffer
}
public override val zero: ViktorNDStructure public override val zero: ViktorNDStructure
get() = F64Array.full(init = 0.0, shape = shape).asStructure() get() = F64Array.full(init = 0.0, shape = shape).asStructure()
public override val one: ViktorNDStructure public override val one: ViktorNDStructure
get() = F64Array.full(init = 1.0, shape = shape).asStructure() get() = F64Array.full(init = 1.0, shape = shape).asStructure()
public val strides: Strides = DefaultStrides(shape) private val strides: Strides = DefaultStrides(shape)
public override val elementContext: RealField get() = RealField public override val elementContext: RealField get() = RealField
@ -42,47 +55,67 @@ public class ViktorNDField(public override val shape: IntArray) : NDField<Double
} }
}.asStructure() }.asStructure()
public override fun map(arg: ViktorNDStructure, transform: RealField.(Double) -> Double): ViktorNDStructure = public override fun NDStructure<Double>.map(transform: RealField.(Double) -> Double): ViktorNDStructure =
F64Array(*shape).apply { F64Array(*this@ViktorNDField.shape).apply {
this@ViktorNDField.strides.indices().forEach { index -> this@ViktorNDField.strides.indices().forEach { index ->
set(value = RealField.transform(arg[index]), indices = index) set(value = RealField.transform(this@map[index]), indices = index)
} }
}.asStructure() }.asStructure()
public override fun mapIndexed( public override fun NDStructure<Double>.mapIndexed(
arg: ViktorNDStructure, transform: RealField.(index: IntArray, Double) -> Double,
transform: RealField.(index: IntArray, Double) -> Double ): ViktorNDStructure = F64Array(*this@ViktorNDField.shape).apply {
): ViktorNDStructure = F64Array(*shape).apply {
this@ViktorNDField.strides.indices().forEach { index -> 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() }.asStructure()
public override fun combine( public override fun combine(
a: ViktorNDStructure, a: NDStructure<Double>,
b: ViktorNDStructure, b: NDStructure<Double>,
transform: RealField.(Double, Double) -> Double transform: RealField.(Double, Double) -> Double,
): ViktorNDStructure = F64Array(*shape).apply { ): ViktorNDStructure = F64Array(*shape).apply {
this@ViktorNDField.strides.indices().forEach { index -> this@ViktorNDField.strides.indices().forEach { index ->
set(value = RealField.transform(a[index], b[index]), indices = index) set(value = RealField.transform(a[index], b[index]), indices = index)
} }
}.asStructure() }.asStructure()
public override fun add(a: ViktorNDStructure, b: ViktorNDStructure): ViktorNDStructure = public override fun add(a: NDStructure<Double>, b: NDStructure<Double>): ViktorNDStructure =
(a.f64Buffer + b.f64Buffer).asStructure() (a.f64Buffer + b.f64Buffer).asStructure()
public override fun multiply(a: ViktorNDStructure, k: Number): ViktorNDStructure = public override fun multiply(a: NDStructure<Double>, k: Number): ViktorNDStructure =
(a.f64Buffer * k.toDouble()).asStructure() (a.f64Buffer * k.toDouble()).asStructure()
public override inline fun ViktorNDStructure.plus(b: ViktorNDStructure): ViktorNDStructure = public override inline fun NDStructure<Double>.plus(b: NDStructure<Double>): ViktorNDStructure =
(f64Buffer + b.f64Buffer).asStructure() (f64Buffer + b.f64Buffer).asStructure()
public override inline fun ViktorNDStructure.minus(b: ViktorNDStructure): ViktorNDStructure = public override inline fun NDStructure<Double>.minus(b: NDStructure<Double>): ViktorNDStructure =
(f64Buffer - b.f64Buffer).asStructure() (f64Buffer - b.f64Buffer).asStructure()
public override inline fun ViktorNDStructure.times(k: Number): ViktorNDStructure = public override inline fun NDStructure<Double>.times(k: Number): ViktorNDStructure =
(f64Buffer * k.toDouble()).asStructure() (f64Buffer * k.toDouble()).asStructure()
public override inline fun ViktorNDStructure.plus(arg: Double): ViktorNDStructure = public override inline fun NDStructure<Double>.plus(arg: Double): ViktorNDStructure =
(f64Buffer.plus(arg)).asStructure() (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)