diff --git a/CHANGELOG.md b/CHANGELOG.md index e21661bd9..e7534fb0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,9 +33,11 @@ - Use `Point` instead of specialized type in `kmath-for-real` - Optimized dot product for buffer matrices moved to `kmath-for-real` - EjmlMatrix context is an object -- Matrix LUP `inverse` renamed to `inverseWithLUP` +- Matrix LUP `inverse` renamed to `inverseWithLup` - `NumericAlgebra` moved outside of regular algebra chain (`Ring` no longer implements it). - Features moved to NDStructure and became transparent. +- 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 diff --git a/README.md b/README.md index 0899f77cc..258d79be5 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ submit a feature request if you want something to be implemented first. > > **Features:** > - [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 > - [expressions](kmath-core/src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions > - [domains](kmath-core/src/commonMain/kotlin/kscience/kmath/domains) : Domains diff --git a/build.gradle.kts b/build.gradle.kts index d171bd608..0572217af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { 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 githubProject: String by extra("kmath") diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 1538e231b..49156e20a 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -67,11 +67,12 @@ benchmark { targets.register("benchmarks") // This one matches sourceSet name above - configurations.register("fast") { + configurations.register("dot") { warmups = 1 // number of warmup iterations iterations = 3 // number of iterations iterationTime = 500 // time in seconds per iteration iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds + include("DotBenchmark") } } diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt index 5c59afaee..2256a3e02 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt @@ -3,14 +3,12 @@ package kscience.kmath.benchmarks import kotlinx.benchmark.Benchmark import kscience.kmath.commons.linear.CMMatrixContext import kscience.kmath.ejml.EjmlMatrixContext - import kscience.kmath.linear.BufferMatrixContext +import kscience.kmath.linear.Matrix import kscience.kmath.linear.RealMatrixContext -import kscience.kmath.linear.real import kscience.kmath.operations.RealField import kscience.kmath.operations.invoke import kscience.kmath.structures.Buffer -import kscience.kmath.structures.Matrix import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State import kotlin.random.Random @@ -33,38 +31,35 @@ class DotBenchmark { } @Benchmark - fun commonsMathMultiplication() { + fun cmDot() { CMMatrixContext { cmMatrix1 dot cmMatrix2 } } @Benchmark - fun ejmlMultiplication() { + fun ejmlDot() { EjmlMatrixContext { ejmlMatrix1 dot ejmlMatrix2 } } @Benchmark - fun ejmlMultiplicationwithConversion() { + fun ejmlDotWithConversion() { EjmlMatrixContext { - val ejmlMatrix1 = matrix1.toEjml() - val ejmlMatrix2 = matrix2.toEjml() - - ejmlMatrix1 dot ejmlMatrix2 + matrix1 dot matrix2 } } @Benchmark - fun bufferedMultiplication() { + fun bufferedDot() { BufferMatrixContext(RealField, Buffer.Companion::real).invoke { matrix1 dot matrix2 } } @Benchmark - fun realMultiplication() { + fun realDot() { RealMatrixContext { matrix1 dot matrix2 } diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt index 5ff43ef80..283210174 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt @@ -26,8 +26,8 @@ class LinearAlgebraBenchmark { } @Benchmark - fun kmathLUPInversion() { - MatrixContext.real.inverseWithLUP(matrix) + fun kmathLupInversion() { + MatrixContext.real.inverseWithLup(matrix) } @Benchmark diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt index 1be8e7236..e465403ad 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt @@ -1,8 +1,9 @@ package kscience.kmath.benchmarks +import kscience.kmath.nd.* import kscience.kmath.operations.RealField 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.Scope import org.openjdk.jmh.annotations.State @@ -11,22 +12,16 @@ import org.openjdk.jmh.annotations.State internal class NDFieldBenchmark { @Benchmark fun autoFieldAdd() { - bufferedField { - var res: NDBuffer = one + autoField { + var res: NDStructure = one repeat(n) { res += one } } } - @Benchmark - fun autoElementAdd() { - var res = genericField.one - repeat(n) { res += 1.0 } - } - @Benchmark fun specializedFieldAdd() { specializedField { - var res: NDBuffer = one + var res: NDStructure = one repeat(n) { res += 1.0 } } } @@ -35,16 +30,16 @@ internal class NDFieldBenchmark { @Benchmark fun boxingFieldAdd() { genericField { - var res: NDBuffer = one - repeat(n) { res += one } + var res: NDStructure = one + repeat(n) { res += 1.0 } } } companion object { const val dim: Int = 1000 const val n: Int = 100 - val bufferedField: BufferedNDField = NDField.auto(RealField, dim, dim) - val specializedField: RealNDField = NDField.real(dim, dim) - val genericField: BoxingNDField = NDField.boxing(RealField, dim, dim) + val autoField = NDAlgebra.auto(RealField, dim, dim) + val specializedField: RealNDField = NDAlgebra.real(dim, dim) + val genericField = NDAlgebra.field(RealField, Buffer.Companion::boxing, dim, dim) } } \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt index 8663e353c..e246936f0 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt @@ -1,10 +1,8 @@ package kscience.kmath.benchmarks +import kscience.kmath.nd.* import kscience.kmath.operations.RealField 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 org.jetbrains.bio.viktor.F64Array import org.openjdk.jmh.annotations.Benchmark @@ -17,15 +15,23 @@ internal class ViktorBenchmark { final val n: Int = 100 // automatically build context most suited for given type. - final val autoField: BufferedNDField = NDField.auto(RealField, dim, dim) - final val realField: RealNDField = NDField.real(dim, dim) - final val viktorField: ViktorNDField = ViktorNDField(intArrayOf(dim, dim)) + final val autoField: NDField = NDAlgebra.auto(RealField, dim, dim) + final val realField: RealNDField = NDAlgebra.real(dim, dim) + final val viktorField: ViktorNDField = ViktorNDField(dim, dim) @Benchmark fun automaticFieldAddition() { autoField { - var res = one - repeat(n) { res += one } + var res: NDStructure = one + repeat(n) { res += 1.0 } + } + } + + @Benchmark + fun realFieldAddition() { + realField { + var res: NDStructure = one + repeat(n) { res += 1.0 } } } @@ -33,7 +39,7 @@ internal class ViktorBenchmark { fun viktorFieldAddition() { viktorField { var res = one - repeat(n) { res += one } + repeat(n) { res += 1.0 } } } @@ -43,22 +49,4 @@ internal class ViktorBenchmark { var 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() - } - } } \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt new file mode 100644 index 000000000..b9c39b088 --- /dev/null +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt @@ -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 = 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() + } + } +} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt b/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt index e84fd8df3..821618af5 100644 --- a/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt +++ b/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt @@ -1,18 +1,17 @@ package kscience.kmath.operations -import kscience.kmath.structures.NDElement -import kscience.kmath.structures.NDField -import kscience.kmath.structures.complex +import kscience.kmath.nd.NDAlgebra +import kscience.kmath.nd.complex fun main() { // 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()) } println(element) // 1d element operation - val result = with(NDField.complex(8)) { + val result = with(NDAlgebra.complex(8)) { val a = produce { (it) -> i * it - it.toDouble() } val b = 3 val c = Complex(1.0, 1.0) diff --git a/examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt b/examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt index b69590473..b6cac4b27 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt +++ b/examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt @@ -1,6 +1,9 @@ +@file:Suppress("unused") + package kscience.kmath.structures import kscience.kmath.linear.transpose +import kscience.kmath.nd.* import kscience.kmath.operations.Complex import kscience.kmath.operations.ComplexField import kscience.kmath.operations.invoke @@ -10,12 +13,12 @@ fun main() { val dim = 1000 val n = 1000 - val realField = NDField.real(dim, dim) - val complexField: ComplexNDField = NDField.complex(dim, dim) + val realField = NDAlgebra.real(dim, dim) + val complexField: ComplexNDField = NDAlgebra.complex(dim, dim) val realTime = measureTimeMillis { realField { - var res: NDBuffer = one + var res: NDStructure = one repeat(n) { res += 1.0 } @@ -26,8 +29,10 @@ fun main() { val complexTime = measureTimeMillis { complexField { - var res: NDBuffer = one - repeat(n) { res += 1.0 } + var res: NDStructure = one + repeat(n) { + res += 1.0 + } } } diff --git a/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt b/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt index b5130c92b..1e7a19cf8 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt +++ b/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt @@ -1,9 +1,11 @@ package kscience.kmath.structures import kotlinx.coroutines.GlobalScope +import kscience.kmath.nd.* import kscience.kmath.nd4j.Nd4jArrayField import kscience.kmath.operations.RealField import kscience.kmath.operations.invoke +import kscience.kmath.viktor.ViktorNDField import org.nd4j.linalg.factory.Nd4j import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -22,42 +24,62 @@ fun main() { val n = 1000 // 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 - val specializedField = NDField.real(dim, dim) + val realField = NDAlgebra.real(dim, dim) //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. 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") { - autoField { - var res: NDBuffer = one + measureAndPrint("Boxing addition") { + boxingField { + var res: NDStructure = one repeat(n) { res += 1.0 } } } - measureAndPrint("Element addition") { - var res = genericField.one - repeat(n) { res += 1.0 } - } - measureAndPrint("Specialized addition") { - specializedField { - var res: NDBuffer = one + realField { + var res: NDStructure = one repeat(n) { res += 1.0 } } } measureAndPrint("Nd4j specialized addition") { nd4jField { - var res = one + var res: NDStructure = one + repeat(n) { res += 1.0 } + } + } + + measureAndPrint("Viktor addition") { + viktorField { + var res: NDStructure = one + repeat(n) { res += 1.0 } + } + } + + measureAndPrint("Parallel stream addition") { + parallelField { + var res: NDStructure = one + repeat(n) { res += 1.0 } + } + } + + measureAndPrint("Automatic field addition") { + autoField { + var res: NDStructure = one repeat(n) { res += 1.0 } } } measureAndPrint("Lazy addition") { - val res = specializedField.one.mapAsync(GlobalScope) { + val res = realField.one.mapAsync(GlobalScope) { var c = 0.0 repeat(n) { c += 1.0 @@ -67,14 +89,4 @@ fun main() { res.elements().forEach { it.second } } - - measureAndPrint("Generic addition") { - //genericField.run(action) - genericField { - var res: NDBuffer = one - repeat(n) { - res += 1.0 // couldn't avoid using `one` due to resolution ambiguity } - } - } - } } diff --git a/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt b/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt new file mode 100644 index 000000000..48286c140 --- /dev/null +++ b/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt @@ -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, + RingWithNumbers>, + ExtendedField> { + + private val strides = DefaultStrides(shape) + override val elementContext: RealField get() = RealField + override val zero: NDBuffer by lazy { produce { zero } } + override val one: NDBuffer by lazy { produce { one } } + + override fun number(value: Number): NDBuffer { + val d = value.toDouble() // minimize conversions + return produce { d } + } + + private val NDStructure.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 { + 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.map( + transform: RealField.(Double) -> Double, + ): NDBuffer { + val array = Arrays.stream(buffer.array).parallel().map { RealField.transform(it) }.toArray() + return NDBuffer(strides, array.asBuffer()) + } + + override fun NDStructure.mapIndexed( + transform: RealField.(index: IntArray, Double) -> Double, + ): NDBuffer { + 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, + b: NDStructure, + transform: RealField.(Double, Double) -> Double, + ): NDBuffer { + 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, pow: Number): NDBuffer = arg.map() { power(it, pow) } + + override fun exp(arg: NDStructure): NDBuffer = arg.map() { exp(it) } + + override fun ln(arg: NDStructure): NDBuffer = arg.map() { ln(it) } + + override fun sin(arg: NDStructure): NDBuffer = arg.map() { sin(it) } + override fun cos(arg: NDStructure): NDBuffer = arg.map() { cos(it) } + override fun tan(arg: NDStructure): NDBuffer = arg.map() { tan(it) } + override fun asin(arg: NDStructure): NDBuffer = arg.map() { asin(it) } + override fun acos(arg: NDStructure): NDBuffer = arg.map() { acos(it) } + override fun atan(arg: NDStructure): NDBuffer = arg.map() { atan(it) } + + override fun sinh(arg: NDStructure): NDBuffer = arg.map() { sinh(it) } + override fun cosh(arg: NDStructure): NDBuffer = arg.map() { cosh(it) } + override fun tanh(arg: NDStructure): NDBuffer = arg.map() { tanh(it) } + override fun asinh(arg: NDStructure): NDBuffer = arg.map() { asinh(it) } + override fun acosh(arg: NDStructure): NDBuffer = arg.map() { acosh(it) } + override fun atanh(arg: NDStructure): NDBuffer = arg.map() { atanh(it) } +} + +fun NDAlgebra.Companion.realWithStream(vararg shape: Int): StreamRealNDField = StreamRealNDField(shape) \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt b/examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt index 51fd4f956..ba7bcb5f2 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt +++ b/examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt @@ -1,5 +1,7 @@ package kscience.kmath.structures +import kscience.kmath.nd.DefaultStrides +import kscience.kmath.nd.NDBuffer import kotlin.system.measureTimeMillis fun main() { @@ -7,7 +9,7 @@ fun main() { val array = DoubleArray(n * n) { 1.0 } val buffer = RealBuffer(array) val strides = DefaultStrides(intArrayOf(n, n)) - val structure = BufferNDStructure(strides, buffer) + val structure = NDBuffer(strides, buffer) measureTimeMillis { var res = 0.0 diff --git a/examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt b/examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt index db55b454f..307b068df 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt +++ b/examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt @@ -1,5 +1,7 @@ package kscience.kmath.structures +import kscience.kmath.nd.NDStructure +import kscience.kmath.nd.mapToBuffer import kotlin.system.measureTimeMillis fun main() { diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt index 850446afa..811c0c2d5 100644 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt @@ -1,11 +1,8 @@ package kscience.kmath.commons.linear -import kscience.kmath.linear.DiagonalFeature -import kscience.kmath.linear.MatrixContext -import kscience.kmath.linear.Point -import kscience.kmath.linear.origin +import kscience.kmath.linear.* import kscience.kmath.misc.UnstableKMathAPI -import kscience.kmath.structures.Matrix +import kscience.kmath.structures.RealBuffer import org.apache.commons.math3.linear.* import kotlin.reflect.KClass import kotlin.reflect.cast @@ -17,8 +14,40 @@ public inline class CMMatrix(public val origin: RealMatrix) : Matrix { @UnstableKMathAPI override fun getFeature(type: KClass): T? = when (type) { DiagonalFeature::class -> if (origin is DiagonalMatrix) DiagonalFeature else null + + DeterminantFeature::class, LupDecompositionFeature::class -> object : + DeterminantFeature, + LupDecompositionFeature { + private val lup by lazy { LUDecomposition(origin) } + override val determinant: Double by lazy { lup.determinant } + override val l: Matrix by lazy { CMMatrix(lup.l) + LFeature } + override val u: Matrix by lazy { CMMatrix(lup.u) + UFeature } + override val p: Matrix by lazy { CMMatrix(lup.p) } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { + override val l: Matrix by lazy { + val cholesky = CholeskyDecomposition(origin) + CMMatrix(cholesky.l) + LFeature + } + } + + QRDecompositionFeature::class -> object : QRDecompositionFeature { + private val qr by lazy { QRDecomposition(origin) } + override val q: Matrix by lazy { CMMatrix(qr.q) + OrthogonalFeature } + override val r: Matrix by lazy { CMMatrix(qr.r) + UFeature } + } + + SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { + private val sv by lazy { SingularValueDecomposition(origin) } + override val u: Matrix by lazy { CMMatrix(sv.u) } + override val s: Matrix by lazy { CMMatrix(sv.s) } + override val v: Matrix by lazy { CMMatrix(sv.v) } + override val singularValues: Point by lazy { RealBuffer(sv.singularValues) } + } + else -> null - }?.let { type.cast(it) } + }?.let(type::cast) public override operator fun get(i: Int, j: Int): Double = origin.getEntry(i, j) } diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt index 210014e1a..1c0896597 100644 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt +++ b/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt @@ -1,7 +1,7 @@ package kscience.kmath.commons.linear +import kscience.kmath.linear.Matrix import kscience.kmath.linear.Point -import kscience.kmath.structures.Matrix import org.apache.commons.math3.linear.* public enum class CMDecomposition { diff --git a/kmath-core/README.md b/kmath-core/README.md index 7882e5252..9ed54b9eb 100644 --- a/kmath-core/README.md +++ b/kmath-core/README.md @@ -3,7 +3,7 @@ The core features of KMath: - [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 - [expressions](src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions - [domains](src/commonMain/kotlin/kscience/kmath/domains) : Domains diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt index 6c61c7c7d..fd0661080 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt @@ -1,8 +1,8 @@ package kscience.kmath.expressions import kscience.kmath.linear.Point +import kscience.kmath.nd.Structure2D import kscience.kmath.structures.BufferFactory -import kscience.kmath.structures.Structure2D /** * An environment to easy transform indexed variables to symbols and back. diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt index a74d948fc..6a66e91c8 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt @@ -1,7 +1,19 @@ package kscience.kmath.linear +import kscience.kmath.nd.NDStructure +import kscience.kmath.nd.Structure2D 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 = Structure2D /** * Basic implementation of Matrix space based on [NDStructure] @@ -17,6 +29,62 @@ public class BufferMatrixContext>( public override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) + private fun Matrix.toBufferMatrix(): BufferMatrix = if (this is BufferMatrix) this else { + produce(rowNum, colNum) { i, j -> get(i, j) } + } + + public fun one(rows: Int, columns: Int): Matrix = VirtualMatrix(rows, columns) { i, j -> + if (i == j) 1.0 else 0.0 + } + DiagonalFeature + + public override infix fun Matrix.dot(other: Matrix): BufferMatrix { + 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.dot(vector: Point): Point { + 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, b: Matrix): BufferMatrix { + 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, k: Number): BufferMatrix { + val aBufferMatrix = a.toBufferMatrix() + return elementContext { + produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() } + } + } + public companion object } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt index 034decc2f..d9201d0f1 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt @@ -1,7 +1,6 @@ package kscience.kmath.linear import kscience.kmath.structures.Buffer -import kscience.kmath.structures.Matrix import kscience.kmath.structures.VirtualBuffer public typealias Point = Buffer diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LupDecomposition.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LupDecomposition.kt index 5cf7c8f70..7c7422c94 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LupDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LupDecomposition.kt @@ -1,6 +1,7 @@ package kscience.kmath.linear import kscience.kmath.misc.UnstableKMathAPI +import kscience.kmath.nd.getFeature import kscience.kmath.operations.* import kscience.kmath.structures.* @@ -151,7 +152,7 @@ public inline fun , F : Field> GenericMatrixContext public fun MatrixContext>.lup(matrix: Matrix): LupDecomposition = lup(Buffer.Companion::real, RealField, matrix) { it < 1e-11 } -public fun LupDecomposition.solveWithLUP( +public fun LupDecomposition.solveWithLup( factory: MutableBufferFactory, matrix: Matrix, ): Matrix { @@ -199,14 +200,14 @@ public fun LupDecomposition.solveWithLUP( } } -public inline fun LupDecomposition.solveWithLUP(matrix: Matrix): Matrix = - solveWithLUP(MutableBuffer.Companion::auto, matrix) +public inline fun LupDecomposition.solveWithLup(matrix: Matrix): Matrix = + solveWithLup(MutableBuffer.Companion::auto, matrix) /** - * Solve a linear equation **a*x = b** using LUP decomposition + * Solves a system of linear equations *ax = b** using LUP decomposition. */ @OptIn(UnstableKMathAPI::class) -public inline fun , F : Field> GenericMatrixContext>.solveWithLUP( +public inline fun , F : Field> GenericMatrixContext>.solveWithLup( a: Matrix, b: Matrix, noinline bufferFactory: MutableBufferFactory = MutableBuffer.Companion::auto, @@ -214,26 +215,26 @@ public inline fun , F : Field> GenericMatrixContext ): Matrix { // Use existing decomposition if it is provided by matrix val decomposition = a.getFeature() ?: lup(bufferFactory, elementContext, a, checkSingular) - return decomposition.solveWithLUP(bufferFactory, b) + return decomposition.solveWithLup(bufferFactory, b) } -public inline fun , F : Field> GenericMatrixContext>.inverseWithLUP( +public inline fun , F : Field> GenericMatrixContext>.inverseWithLup( matrix: Matrix, noinline bufferFactory: MutableBufferFactory = MutableBuffer.Companion::auto, noinline checkSingular: (T) -> Boolean, -): Matrix = solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular) +): Matrix = solveWithLup(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular) @OptIn(UnstableKMathAPI::class) -public fun RealMatrixContext.solveWithLUP(a: Matrix, b: Matrix): Matrix { +public fun RealMatrixContext.solveWithLup(a: Matrix, b: Matrix): Matrix { // Use existing decomposition if it is provided by matrix val bufferFactory: MutableBufferFactory = MutableBuffer.Companion::real val decomposition: LupDecomposition = a.getFeature() ?: lup(bufferFactory, RealField, a) { it < 1e-11 } - return decomposition.solveWithLUP(bufferFactory, b) + return decomposition.solveWithLup(bufferFactory, b) } /** * Inverses a square matrix using LUP decomposition. Non square matrix will throw a error. */ -public fun RealMatrixContext.inverseWithLUP(matrix: Matrix): Matrix = - solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum)) \ No newline at end of file +public fun RealMatrixContext.inverseWithLup(matrix: Matrix): Matrix = + solveWithLup(matrix, one(matrix.rowNum, matrix.colNum)) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt index c0c209248..cb01939cc 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt @@ -1,6 +1,9 @@ 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 operator fun invoke(vararg elements: T): Matrix { @@ -22,7 +25,7 @@ public fun Structure2D.Companion.row(vararg values: T): Matrix { public inline fun Structure2D.Companion.row( size: Int, factory: BufferFactory = Buffer.Companion::auto, - noinline builder: (Int) -> T + noinline builder: (Int) -> T, ): Matrix { val buffer = factory(size, builder) return BufferMatrix(1, size, buffer) @@ -36,7 +39,7 @@ public fun Structure2D.Companion.column(vararg values: T): Matrix { public inline fun Structure2D.Companion.column( size: Int, factory: BufferFactory = Buffer.Companion::auto, - noinline builder: (Int) -> T + noinline builder: (Int) -> T, ): Matrix { val buffer = factory(size, builder) return BufferMatrix(size, 1, buffer) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt index ad1674c43..d265bad6d 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt @@ -7,7 +7,6 @@ import kscience.kmath.operations.invoke import kscience.kmath.operations.sum import kscience.kmath.structures.Buffer import kscience.kmath.structures.BufferFactory -import kscience.kmath.structures.Matrix import kscience.kmath.structures.asSequence import kotlin.reflect.KClass diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt index e61feec6c..196c46623 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt @@ -1,7 +1,5 @@ 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 * to optimize matrix operations performance in some cases or retrieve the APIs. @@ -11,8 +9,8 @@ public interface MatrixFeature /** * Matrices with this feature are considered to have only diagonal non-null elements. */ -public interface DiagonalFeature : MatrixFeature{ - public companion object: DiagonalFeature +public interface DiagonalFeature : MatrixFeature { + public companion object : DiagonalFeature } /** @@ -39,6 +37,8 @@ public interface InverseMatrixFeature : MatrixFeature { /** * Matrices with this feature can compute their determinant. + * + * @param T the type of matrices' items. */ public interface DeterminantFeature : MatrixFeature { /** diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt index 362db1fe7..df967e3c1 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt @@ -1,11 +1,10 @@ package kscience.kmath.linear import kscience.kmath.misc.UnstableKMathAPI +import kscience.kmath.nd.Structure2D +import kscience.kmath.nd.getFeature import kscience.kmath.operations.Ring -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.Structure2D import kscience.kmath.structures.asBuffer -import kscience.kmath.structures.getFeature import kotlin.math.sqrt import kotlin.reflect.KClass import kotlin.reflect.safeCast @@ -39,7 +38,8 @@ public class MatrixWrapper internal constructor( * Origin does not necessary store all features. */ @UnstableKMathAPI -public val Matrix.origin: Matrix get() = (this as? MatrixWrapper)?.origin ?: this +public val Matrix.origin: Matrix + get() = (this as? MatrixWrapper)?.origin ?: this /** * Add a single feature to a [Matrix] @@ -60,12 +60,6 @@ public operator fun Matrix.plus(newFeatures: Collection Double, -): BufferMatrix = MatrixContext.real.produce(rows, columns, initializer) - /** * Build a square matrix from given elements. */ diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt index 8e197672f..da795f56b 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt @@ -1,12 +1,10 @@ package kscience.kmath.linear -import kscience.kmath.structures.Matrix import kscience.kmath.structures.RealBuffer -@Suppress("OVERRIDE_BY_INLINE") public object RealMatrixContext : MatrixContext> { - public override inline fun produce( + public override fun produce( rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double, @@ -15,7 +13,7 @@ public object RealMatrixContext : MatrixContext> { return BufferMatrix(rows, columns, buffer) } - private fun Matrix.wrap(): BufferMatrix = if (this is BufferMatrix) this else { + public fun Matrix.toBufferMatrix(): BufferMatrix = if (this is BufferMatrix) this else { produce(rowNum, colNum) { i, j -> get(i, j) } } @@ -25,10 +23,12 @@ public object RealMatrixContext : MatrixContext> { public override infix fun Matrix.dot(other: Matrix): BufferMatrix { require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } + val bufferMatrix = toBufferMatrix() + val otherBufferMatrix = other.toBufferMatrix() return produce(rowNum, other.colNum) { i, j -> var res = 0.0 for (l in 0 until colNum) { - res += get(i, l) * other.get(l, j) + res += bufferMatrix[i, l] * otherBufferMatrix[l, j] } res } @@ -36,10 +36,11 @@ public object RealMatrixContext : MatrixContext> { public override infix fun Matrix.dot(vector: Point): Point { require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" } + val bufferMatrix = toBufferMatrix() return RealBuffer(rowNum) { i -> var res = 0.0 for (j in 0 until colNum) { - res += get(i, j) * vector[j] + res += bufferMatrix[i, j] * vector[j] } res } @@ -48,17 +49,23 @@ public object RealMatrixContext : MatrixContext> { override fun add(a: Matrix, b: Matrix): BufferMatrix { require(a.rowNum == b.rowNum) { "Row number mismatch in matrix addition. Left side: ${a.rowNum}, right side: ${b.rowNum}" } require(a.colNum == b.colNum) { "Column number mismatch in matrix addition. Left side: ${a.colNum}, right side: ${b.colNum}" } + val aBufferMatrix = a.toBufferMatrix() + val bBufferMatrix = b.toBufferMatrix() return produce(a.rowNum, a.colNum) { i, j -> - a[i, j] + b[i, j] + aBufferMatrix[i, j] + bBufferMatrix[i, j] } } - override fun Matrix.times(value: Double): BufferMatrix = - produce(rowNum, colNum) { i, j -> get(i, j) * value } + override fun Matrix.times(value: Double): BufferMatrix { + val bufferMatrix = toBufferMatrix() + return produce(rowNum, colNum) { i, j -> bufferMatrix[i, j] * value } + } - override fun multiply(a: Matrix, k: Number): BufferMatrix = - produce(a.rowNum, a.colNum) { i, j -> a[i, j] * k.toDouble() } + override fun multiply(a: Matrix, k: Number): BufferMatrix { + val aBufferMatrix = a.toBufferMatrix() + return produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() } + } } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt index 0269a64d1..df4e4de09 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt @@ -1,7 +1,5 @@ package kscience.kmath.linear -import kscience.kmath.structures.Matrix - public class VirtualMatrix( override val rowNum: Int, override val colNum: Int, diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt index d70ac7b39..06248a166 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt @@ -1,4 +1,4 @@ package kscience.kmath.misc @RequiresOptIn("This API is unstable and could change in future", RequiresOptIn.Level.WARNING) -public annotation class UnstableKMathAPI \ No newline at end of file +public annotation class UnstableKMathAPI diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt new file mode 100644 index 000000000..2d72162a6 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt @@ -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 : NDAlgebra { + public val strides: Strides + public val bufferFactory: BufferFactory + + override fun produce(initializer: C.(IntArray) -> T): NDBuffer = NDBuffer( + strides, + bufferFactory(strides.linearSize) { offset -> + elementContext.initializer(strides.index(offset)) + } + ) + + public val NDStructure.buffer: Buffer + 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.map(transform: C.(T) -> T): NDBuffer { + val buffer = bufferFactory(strides.linearSize) { offset -> + elementContext.transform(buffer[offset]) + } + return NDBuffer(strides, buffer) + } + + override fun NDStructure.mapIndexed(transform: C.(index: IntArray, T) -> T): NDBuffer { + val buffer = bufferFactory(strides.linearSize) { offset -> + elementContext.transform( + strides.index(offset), + buffer[offset] + ) + } + return NDBuffer(strides, buffer) + } + + override fun combine(a: NDStructure, b: NDStructure, transform: C.(T, T) -> T): NDBuffer { + val buffer = bufferFactory(strides.linearSize) { offset -> + elementContext.transform(a.buffer[offset], b.buffer[offset]) + } + return NDBuffer(strides, buffer) + } +} + +public open class BufferedNDSpace>( + final override val shape: IntArray, + final override val elementContext: R, + final override val bufferFactory: BufferFactory, +) : NDSpace, BufferNDAlgebra { + override val strides: Strides = DefaultStrides(shape) + override val zero: NDBuffer by lazy { produce { zero } } +} + +public open class BufferedNDRing>( + shape: IntArray, + elementContext: R, + bufferFactory: BufferFactory, +) : BufferedNDSpace(shape, elementContext, bufferFactory), NDRing { + override val one: NDBuffer by lazy { produce { one } } +} + +public open class BufferedNDField>( + shape: IntArray, + elementContext: R, + bufferFactory: BufferFactory, +) : BufferedNDRing(shape, elementContext, bufferFactory), NDField + +// space factories +public fun > NDAlgebra.Companion.space( + space: A, + bufferFactory: BufferFactory, + vararg shape: Int, +): BufferedNDSpace = BufferedNDSpace(shape, space, bufferFactory) + +public inline fun , R> A.ndSpace( + noinline bufferFactory: BufferFactory, + vararg shape: Int, + action: BufferedNDSpace.() -> R, +): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return NDAlgebra.space(this, bufferFactory, *shape).run(action) +} + +//ring factories +public fun > NDAlgebra.Companion.ring( + ring: A, + bufferFactory: BufferFactory, + vararg shape: Int, +): BufferedNDRing = BufferedNDRing(shape, ring, bufferFactory) + +public inline fun , R> A.ndRing( + noinline bufferFactory: BufferFactory, + vararg shape: Int, + action: BufferedNDRing.() -> R, +): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return NDAlgebra.ring(this, bufferFactory, *shape).run(action) +} + +//field factories +public fun > NDAlgebra.Companion.field( + field: A, + bufferFactory: BufferFactory, + vararg shape: Int, +): BufferedNDField = BufferedNDField(shape, field, bufferFactory) + +@Suppress("UNCHECKED_CAST") +public inline fun > NDAlgebra.Companion.auto( + field: A, + vararg shape: Int, +): NDField = when (field) { + RealField -> RealNDField(shape) as NDField + else -> BufferedNDField(shape, field, Buffer.Companion::auto) +} + +public inline fun , R> A.ndField( + noinline bufferFactory: BufferFactory, + vararg shape: Int, + action: BufferedNDField.() -> R, +): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return NDAlgebra.field(this, bufferFactory, *shape).run(action) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt new file mode 100644 index 000000000..00e79f2e4 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt @@ -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(shape, ComplexField, Buffer.Companion::complex), + RingWithNumbers>, + ExtendedField> { + + override val zero: NDBuffer by lazy { produce { zero } } + override val one: NDBuffer by lazy { produce { one } } + + override fun number(value: Number): NDBuffer { + val d = value.toComplex() // minimize conversions + return produce { d } + } +// +// @Suppress("OVERRIDE_BY_INLINE") +// override inline fun map( +// arg: AbstractNDBuffer, +// 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, +// 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, +// b: AbstractNDBuffer, +// 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, pow: Number): NDBuffer = arg.map() { power(it, pow) } + + override fun exp(arg: NDStructure): NDBuffer = arg.map() { exp(it) } + + override fun ln(arg: NDStructure): NDBuffer = arg.map() { ln(it) } + + override fun sin(arg: NDStructure): NDBuffer = arg.map() { sin(it) } + override fun cos(arg: NDStructure): NDBuffer = arg.map() { cos(it) } + override fun tan(arg: NDStructure): NDBuffer = arg.map() { tan(it) } + override fun asin(arg: NDStructure): NDBuffer = arg.map() { asin(it) } + override fun acos(arg: NDStructure): NDBuffer = arg.map() { acos(it) } + override fun atan(arg: NDStructure): NDBuffer = arg.map() { atan(it) } + + override fun sinh(arg: NDStructure): NDBuffer = arg.map() { sinh(it) } + override fun cosh(arg: NDStructure): NDBuffer = arg.map() { cosh(it) } + override fun tanh(arg: NDStructure): NDBuffer = arg.map() { tanh(it) } + override fun asinh(arg: NDStructure): NDBuffer = arg.map() { asinh(it) } + override fun acosh(arg: NDStructure): NDBuffer = arg.map() { acosh(it) } + override fun atanh(arg: NDStructure): NDBuffer = arg.map() { atanh(it) } +} + + +/** + * Fast element production using function inlining + */ +public inline fun BufferedNDField.produceInline(initializer: ComplexField.(Int) -> Complex): NDBuffer { + 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 ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return ComplexNDField(shape).action() +} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt new file mode 100644 index 000000000..749fb1a13 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt @@ -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 { + /** + * 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 + + /** + * Maps elements from one structure to another one by applying [transform] to them. + */ + public fun NDStructure.map(transform: C.(T) -> T): NDStructure + + /** + * Maps elements from one structure to another one by applying [transform] to them alongside with their indices. + */ + public fun NDStructure.mapIndexed(transform: C.(index: IntArray, T) -> T): NDStructure + + /** + * Combines two structures into one. + */ + public fun combine(a: NDStructure, b: NDStructure, transform: C.(T, T) -> T): NDStructure + + /** + * Element-wise invocation of function working on [T] on a [NDStructure]. + */ + public operator fun Function1.invoke(structure: NDStructure): NDStructure = + 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 NDAlgebra.checkShape(vararg structures: NDStructure): Array> = structures + .map(NDStructure::shape) + .singleOrNull { !shape.contentEquals(it) } + ?.let>> { 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 NDAlgebra.checkShape(element: NDStructure): NDStructure { + 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> : Space>, NDAlgebra { + /** + * Element-wise addition. + * + * @param a the addend. + * @param b the augend. + * @return the sum. + */ + public override fun add(a: NDStructure, b: NDStructure): NDStructure = + 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, k: Number): NDStructure = 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.plus(arg: T): NDStructure = 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.minus(arg: T): NDStructure = 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): NDStructure = 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): NDStructure = 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> : Ring>, NDSpace { + /** + * Element-wise multiplication. + * + * @param a the multiplicand. + * @param b the multiplier. + * @return the product. + */ + public override fun multiply(a: NDStructure, b: NDStructure): NDStructure = + 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.times(arg: T): NDStructure = 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): NDStructure = 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> : Field>, NDRing { + /** + * Element-wise division. + * + * @param a the dividend. + * @param b the divisor. + * @return the quotient. + */ + public override fun divide(a: NDStructure, b: NDStructure): NDStructure = + 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.div(arg: T): NDStructure = 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): NDStructure = arg.map() { divide(it, this@div) } + +// @ThreadLocal +// public companion object { +// private val realNDFieldCache: MutableMap = 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 > boxing( +// field: F, +// vararg shape: Int, +// bufferFactory: BufferFactory = Buffer.Companion::boxing, +// ): BufferedNDField = BufferedNDField(shape, field, bufferFactory) +// +// /** +// * Create a most suitable implementation for nd-field using reified class. +// */ +// @Suppress("UNCHECKED_CAST") +// public inline fun > auto(field: F, vararg shape: Int): NDField = +// when { +// T::class == Double::class -> real(*shape) as NDField +// T::class == Complex::class -> complex(*shape) as BufferedNDField +// else -> BoxingNDField(shape, field, Buffer.Companion::auto) +// } +// } +} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDStructure.kt similarity index 83% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt rename to kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDStructure.kt index e7d89ca7e..4aa1c7d52 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDStructure.kt @@ -1,6 +1,10 @@ -package kscience.kmath.structures +package kscience.kmath.nd 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.native.concurrent.ThreadLocal import kotlin.reflect.KClass @@ -74,8 +78,8 @@ public interface NDStructure { strides: Strides, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T, - ): BufferNDStructure = - BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) + ): NDBuffer = + NDBuffer(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** * Inline create NDStructure with non-boxing buffer implementation if it is possible @@ -83,40 +87,40 @@ public interface NDStructure { public inline fun auto( strides: Strides, crossinline initializer: (IntArray) -> T, - ): BufferNDStructure = - BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) + ): NDBuffer = + NDBuffer(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) public inline fun auto( type: KClass, strides: Strides, crossinline initializer: (IntArray) -> T, - ): BufferNDStructure = - BufferNDStructure(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) }) + ): NDBuffer = + NDBuffer(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) }) public fun build( shape: IntArray, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T, - ): BufferNDStructure = build(DefaultStrides(shape), bufferFactory, initializer) + ): NDBuffer = build(DefaultStrides(shape), bufferFactory, initializer) public inline fun auto( shape: IntArray, crossinline initializer: (IntArray) -> T, - ): BufferNDStructure = + ): NDBuffer = auto(DefaultStrides(shape), initializer) @JvmName("autoVarArg") public inline fun auto( vararg shape: Int, crossinline initializer: (IntArray) -> T, - ): BufferNDStructure = + ): NDBuffer = auto(DefaultStrides(shape), initializer) public inline fun auto( type: KClass, vararg shape: Int, crossinline initializer: (IntArray) -> T, - ): BufferNDStructure = + ): NDBuffer = auto(type, DefaultStrides(shape), initializer) } } @@ -156,7 +160,7 @@ public inline fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> */ public interface Strides { /** - * Shape of NDstructure + * Shape of NDStructure */ public val shape: IntArray @@ -185,7 +189,9 @@ public interface Strides { /** * Iterate over ND indices in a natural order */ - public fun indices(): Sequence = (0 until linearSize).asSequence().map { index(it) } + public fun indices(): Sequence = (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 -> - if (value < 0 || value >= this.shape[i]) - throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") - + if (value < 0 || value >= shape[i]) throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") value * strides[i] }.sum() @@ -256,23 +260,29 @@ public class DefaultStrides private constructor(override val shape: IntArray) : * Represents [NDStructure] over [Buffer]. * * @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 : NDStructure { - /** - * The underlying buffer. - */ - public abstract val buffer: Buffer +public open class NDBuffer( + public val strides: Strides, + buffer: Buffer, +) : NDStructure { - /** - * The strides to access elements of [Buffer] by linear indices. - */ - public abstract val strides: Strides + init { + if (strides.linearSize != buffer.size) { + error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") + } + } + + public open val buffer: Buffer = buffer override operator fun get(index: IntArray): T = buffer[strides.offset(index)] override val shape: IntArray get() = strides.shape - override fun elements(): Sequence> = strides.indices().map { it to this[it] } + override fun elements(): Sequence> = strides.indices().map { + it to this[it] + } override fun equals(other: Any?): Boolean { return NDStructure.contentEquals(this, other as? NDStructure<*> ?: return false) @@ -297,46 +307,30 @@ public abstract class NDBuffer : NDStructure { } return "NDBuffer(shape=${shape.contentToString()}, buffer=$bufferRepr)" } - - } /** - * Boxing generic [NDStructure] - */ -public class BufferNDStructure( - override val strides: Strides, - override val buffer: Buffer, -) : NDBuffer() { - 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] + * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [NDBuffer] */ public inline fun NDStructure.mapToBuffer( factory: BufferFactory = Buffer.Companion::auto, crossinline transform: (T) -> R, -): BufferNDStructure { - return if (this is BufferNDStructure) - BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) +): NDBuffer { + return if (this is NDBuffer) + NDBuffer(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) else { 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]. */ -public class MutableBufferNDStructure( - override val strides: Strides, - override val buffer: MutableBuffer, -) : NDBuffer(), MutableNDStructure { +public class MutableNDBuffer( + strides: Strides, + buffer: MutableBuffer, +) : NDBuffer(strides, buffer), MutableNDStructure { init { require(strides.linearSize == buffer.size) { @@ -344,6 +338,8 @@ public class MutableBufferNDStructure( } } + override val buffer: MutableBuffer = super.buffer as MutableBuffer + override operator fun set(index: IntArray, value: T): Unit = buffer.set(strides.offset(index), value) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt new file mode 100644 index 000000000..8e0f9e171 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt @@ -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(shape, RealField, Buffer.Companion::real), + RingWithNumbers>, + ExtendedField> { + + override val zero: NDBuffer by lazy { produce { zero } } + override val one: NDBuffer by lazy { produce { one } } + + override fun number(value: Number): NDBuffer { + val d = value.toDouble() // minimize conversions + return produce { d } + } + + override val NDStructure.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.map( + transform: RealField.(Double) -> Double, + ): NDBuffer { + 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 { + 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.mapIndexed( + transform: RealField.(index: IntArray, Double) -> Double, + ): NDBuffer = 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, + b: NDStructure, + transform: RealField.(Double, Double) -> Double, + ): NDBuffer { + 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, pow: Number): NDBuffer = arg.map { power(it, pow) } + + override fun exp(arg: NDStructure): NDBuffer = arg.map { exp(it) } + + override fun ln(arg: NDStructure): NDBuffer = arg.map { ln(it) } + + override fun sin(arg: NDStructure): NDBuffer = arg.map { sin(it) } + override fun cos(arg: NDStructure): NDBuffer = arg.map { cos(it) } + override fun tan(arg: NDStructure): NDBuffer = arg.map { tan(it) } + override fun asin(arg: NDStructure): NDBuffer = arg.map { asin(it) } + override fun acos(arg: NDStructure): NDBuffer = arg.map { acos(it) } + override fun atan(arg: NDStructure): NDBuffer = arg.map { atan(it) } + + override fun sinh(arg: NDStructure): NDBuffer = arg.map { sinh(it) } + override fun cosh(arg: NDStructure): NDBuffer = arg.map { cosh(it) } + override fun tanh(arg: NDStructure): NDBuffer = arg.map { tanh(it) } + override fun asinh(arg: NDStructure): NDBuffer = arg.map { asinh(it) } + override fun acosh(arg: NDStructure): NDBuffer = arg.map { acosh(it) } + override fun atanh(arg: NDStructure): NDBuffer = 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 RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return RealNDField(shape).run(action) +} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ShortNDRing.kt new file mode 100644 index 000000000..7ffe688cb --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ShortNDRing.kt @@ -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(shape, ShortRing, Buffer.Companion::auto), + RingWithNumbers> { + + override val zero: NDBuffer by lazy { produce { zero } } + override val one: NDBuffer by lazy { produce { one } } + + override fun number(value: Number): NDBuffer { + val d = value.toShort() // minimize conversions + return produce { d } + } +} + +/** + * Fast element production using function inlining. + */ +public inline fun BufferedNDRing.produceInline(crossinline initializer: ShortRing.(Int) -> Short): NDBuffer { + return NDBuffer(strides, ShortBuffer(ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) })) +} + +public inline fun ShortRing.nd(vararg shape: Int, action: ShortNDRing.() -> R): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return ShortNDRing(shape).run(action) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure1D.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure1D.kt similarity index 89% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure1D.kt rename to kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure1D.kt index 95422ac60..d69c23fb4 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure1D.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure1D.kt @@ -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 @@ -34,7 +37,7 @@ private inline class Buffer1DWrapper(val buffer: Buffer) : Structure1D override val size: Int get() = buffer.size override fun elements(): Sequence> = - 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] } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure2D.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt similarity index 81% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure2D.kt rename to kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt index d20e9e53b..32e0704fc 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure2D.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt @@ -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. @@ -49,7 +54,15 @@ public interface Structure2D : NDStructure { 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 = RealMatrixContext.produce(rows,columns) { i, j -> + init(i, j) + } + } } /** @@ -73,10 +86,3 @@ public fun NDStructure.as2D(): Structure2D = if (shape.size == 2) Structure2DWrapper(this) else 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 = Structure2D diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt index 6f16838b0..7e26a2899 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt @@ -1,9 +1,12 @@ package kscience.kmath.operations 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_SIZE -import kscience.kmath.structures.* +import kscience.kmath.structures.Buffer +import kscience.kmath.structures.MutableBuffer import kotlin.math.log2 import kotlin.math.max 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 = boxing(size, initializer) -public fun NDAlgebra.Companion.bigInt(vararg shape: Int): BoxingNDRing = - BoxingNDRing(shape, BigIntField, Buffer.Companion::bigInt) - -public fun NDElement.Companion.bigInt( - vararg shape: Int, - initializer: BigIntField.(IntArray) -> BigInt -): BufferedNDRingElement = NDAlgebra.bigInt(*shape).produce(initializer) +public fun NDAlgebra.Companion.bigInt(vararg shape: Int): BufferedNDRing = + BufferedNDRing(shape, BigIntField, Buffer.Companion::bigInt) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDField.kt deleted file mode 100644 index dc65b12c4..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDField.kt +++ /dev/null @@ -1,81 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.Field -import kscience.kmath.operations.FieldElement - -public class BoxingNDField>( - public override val shape: IntArray, - public override val elementContext: F, - public val bufferFactory: BufferFactory -) : BufferedNDField { - public override val zero: BufferedNDFieldElement by lazy { produce { zero } } - public override val one: BufferedNDFieldElement by lazy { produce { one } } - public override val strides: Strides = DefaultStrides(shape) - - public fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = - bufferFactory(size, initializer) - - public override fun check(vararg elements: NDBuffer): Array> { - 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 = - BufferedNDFieldElement( - this, - buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - - public override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferedNDFieldElement { - 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, - transform: F.(index: IntArray, T) -> T - ): BufferedNDFieldElement { - 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, - b: NDBuffer, - transform: F.(T, T) -> T - ): BufferedNDFieldElement { - check(a, b) - return BufferedNDFieldElement( - this, - buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) - } - - public override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = - BufferedNDFieldElement(this@BoxingNDField, buffer) -} - -public inline fun , R> F.nd( - noinline bufferFactory: BufferFactory, - vararg shape: Int, - action: NDField.() -> R -): R { - val ndfield = NDField.boxing(this, *shape, bufferFactory = bufferFactory) - return ndfield.action() -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDRing.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDRing.kt deleted file mode 100644 index b6794984c..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDRing.kt +++ /dev/null @@ -1,71 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.Ring -import kscience.kmath.operations.RingElement - -public class BoxingNDRing>( - override val shape: IntArray, - override val elementContext: R, - public val bufferFactory: BufferFactory -) : BufferedNDRing { - override val strides: Strides = DefaultStrides(shape) - override val zero: BufferedNDRingElement by lazy { produce { zero } } - override val one: BufferedNDRingElement by lazy { produce { one } } - - public fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) - - override fun check(vararg elements: NDBuffer): Array> { - 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 = - BufferedNDRingElement( - this, - buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - - override fun map(arg: NDBuffer, transform: R.(T) -> T): BufferedNDRingElement { - 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, - transform: R.(index: IntArray, T) -> T - ): BufferedNDRingElement { - 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, - b: NDBuffer, - transform: R.(T, T) -> T - ): BufferedNDRingElement { - check(a, b) - - return BufferedNDRingElement( - this, - buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) - } - - override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> = - BufferedNDRingElement(this@BoxingNDRing, buffer) -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt index 5d7ba611f..3c53d6c51 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt @@ -1,5 +1,10 @@ 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 */ diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDAlgebra.kt deleted file mode 100644 index 3dcd0322c..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDAlgebra.kt +++ /dev/null @@ -1,43 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.* - -public interface BufferedNDAlgebra : NDAlgebra> { - public val strides: Strides - - public override fun check(vararg elements: NDBuffer): Array> { - 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.toBuffer(): NDBuffer = - if (this is NDBuffer && this.strides == this@BufferedNDAlgebra.strides) - this - else - produce { index -> this@toBuffer[index] } - - /** - * Convert a buffer to element of this algebra - */ - public fun NDBuffer.toElement(): MathElement> -} - - -public interface BufferedNDSpace> : NDSpace>, BufferedNDAlgebra { - public override fun NDBuffer.toElement(): SpaceElement, *, out BufferedNDSpace> -} - -public interface BufferedNDRing> : NDRing>, BufferedNDSpace { - override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> -} - -public interface BufferedNDField> : NDField>, BufferedNDRing { - override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDElement.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDElement.kt deleted file mode 100644 index d53702566..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDElement.kt +++ /dev/null @@ -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 : NDBuffer(), NDElement> { - abstract override val context: BufferedNDAlgebra - - override val strides: Strides get() = context.strides - - override val shape: IntArray get() = context.shape -} - -public class BufferedNDSpaceElement>( - override val context: BufferedNDSpace, - override val buffer: Buffer -) : BufferedNDElement(), SpaceElement, BufferedNDSpaceElement, BufferedNDSpace> { - - override fun unwrap(): NDBuffer = this - - override fun NDBuffer.wrap(): BufferedNDSpaceElement { - context.check(this) - return BufferedNDSpaceElement(context, buffer) - } -} - -public class BufferedNDRingElement>( - override val context: BufferedNDRing, - override val buffer: Buffer -) : BufferedNDElement(), RingElement, BufferedNDRingElement, BufferedNDRing> { - override fun unwrap(): NDBuffer = this - - override fun NDBuffer.wrap(): BufferedNDRingElement { - context.check(this) - return BufferedNDRingElement(context, buffer) - } -} - -public class BufferedNDFieldElement>( - override val context: BufferedNDField, - override val buffer: Buffer -) : BufferedNDElement(), FieldElement, BufferedNDFieldElement, BufferedNDField> { - override fun unwrap(): NDBuffer = this - - override fun NDBuffer.wrap(): BufferedNDFieldElement { - 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 > Function1.invoke(ndElement: BufferedNDElement): MathElement> = - ndElement.context.run { map(ndElement) { invoke(it) }.toElement() } - -/* plus and minus */ - -/** - * Summation operation for [BufferedNDElement] and single element - */ -public operator fun > BufferedNDElement.plus(arg: T): NDElement> = - context.map(this) { it + arg }.wrap() - -/** - * Subtraction operation between [BufferedNDElement] and single element - */ -public operator fun > BufferedNDElement.minus(arg: T): NDElement> = - context.map(this) { it - arg }.wrap() - -/* prod and div */ - -/** - * Product operation for [BufferedNDElement] and single element - */ -public operator fun > BufferedNDElement.times(arg: T): NDElement> = - context.map(this) { it * arg }.wrap() - -/** - * Division operation between [BufferedNDElement] and single element - */ -public operator fun > BufferedNDElement.div(arg: T): NDElement> = - context.map(this) { it / arg }.wrap() diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ComplexNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ComplexNDField.kt deleted file mode 100644 index 6de69cabe..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ComplexNDField.kt +++ /dev/null @@ -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 - -/** - * An optimized nd-field for complex numbers - */ -@OptIn(UnstableKMathAPI::class) -public class ComplexNDField(override val shape: IntArray) : - BufferedNDField, - ExtendedNDField>, - RingWithNumbers>{ - - 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 { - val c = value.toComplex() - return produce { c } - } - - public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer = - Buffer.complex(size) { initializer(it) } - - /** - * Inline transform an NDStructure to another structure - */ - override fun map( - arg: NDBuffer, - 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, - 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, - b: NDBuffer, - 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.toElement(): FieldElement, *, out BufferedNDField> = - BufferedNDFieldElement(this@ComplexNDField, buffer) - - override fun power(arg: NDBuffer, pow: Number): ComplexNDElement = - map(arg) { power(it, pow) } - - override fun exp(arg: NDBuffer): ComplexNDElement = map(arg) { exp(it) } - override fun ln(arg: NDBuffer): ComplexNDElement = map(arg) { ln(it) } - - override fun sin(arg: NDBuffer): ComplexNDElement = map(arg) { sin(it) } - override fun cos(arg: NDBuffer): ComplexNDElement = map(arg) { cos(it) } - override fun tan(arg: NDBuffer): ComplexNDElement = map(arg) { tan(it) } - override fun asin(arg: NDBuffer): ComplexNDElement = map(arg) { asin(it) } - override fun acos(arg: NDBuffer): ComplexNDElement = map(arg) { acos(it) } - override fun atan(arg: NDBuffer): ComplexNDElement = map(arg) { atan(it) } - - override fun sinh(arg: NDBuffer): ComplexNDElement = map(arg) { sinh(it) } - override fun cosh(arg: NDBuffer): ComplexNDElement = map(arg) { cosh(it) } - override fun tanh(arg: NDBuffer): ComplexNDElement = map(arg) { tanh(it) } - override fun asinh(arg: NDBuffer): ComplexNDElement = map(arg) { asinh(it) } - override fun acosh(arg: NDBuffer): ComplexNDElement = map(arg) { acosh(it) } - override fun atanh(arg: NDBuffer): ComplexNDElement = map(arg) { atanh(it) } -} - - -/** - * Fast element production using function inlining - */ -public inline fun BufferedNDField.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.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 ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R { - contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } - return NDField.complex(*shape).action() -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ExtendedNDField.kt deleted file mode 100644 index a9fa2763b..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ExtendedNDField.kt +++ /dev/null @@ -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, N : NDStructure> : NDField, ExtendedField - -///** -// * NDField that supports [ExtendedField] operations on its elements -// */ -//class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : -// ExtendedNDField, NDField 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]) } } -// } -//} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDAlgebra.kt deleted file mode 100644 index d7b019c65..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDAlgebra.kt +++ /dev/null @@ -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> { - /** - * 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 = elements - .map(NDStructure::shape) - .singleOrNull { !shape.contentEquals(it) } - ?.let> { throw ShapeMismatchException(shape, it) } - ?: elements - - /** - * Element-wise invocation of function working on [T] on a [NDStructure]. - */ - public operator fun Function1.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, N : NDStructure> : Space, NDAlgebra { - /** - * 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, N : NDStructure> : Ring, NDSpace { - /** - * 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, N : NDStructure> : Field, NDRing { - /** - * 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 = 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 > boxing( - field: F, - vararg shape: Int, - bufferFactory: BufferFactory = Buffer.Companion::boxing - ): BoxingNDField = BoxingNDField(shape, field, bufferFactory) - - /** - * Create a most suitable implementation for nd-field using reified class. - */ - @Suppress("UNCHECKED_CAST") - public inline fun > auto(field: F, vararg shape: Int): BufferedNDField = - when { - T::class == Double::class -> real(*shape) as BufferedNDField - T::class == Complex::class -> complex(*shape) as BufferedNDField - else -> BoxingNDField(shape, field, Buffer.Companion::auto) - } - } -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDElement.kt deleted file mode 100644 index f2f565064..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDElement.kt +++ /dev/null @@ -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> : NDStructure { - public val context: NDAlgebra - - public fun unwrap(): N - - public fun N.wrap(): NDElement - - 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 > boxing( - shape: IntArray, - field: F, - initializer: F.(IntArray) -> T - ): BufferedNDElement { - val ndField = BoxingNDField(shape, field, Buffer.Companion::boxing) - return ndField.produce(initializer) - } - - public inline fun > auto( - shape: IntArray, - field: F, - noinline initializer: F.(IntArray) -> T - ): BufferedNDFieldElement { - val ndField = NDField.auto(field, *shape) - return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer) - } - } -} - -public fun > NDElement.mapIndexed(transform: C.(index: IntArray, T) -> T): NDElement = - context.mapIndexed(unwrap(), transform).wrap() - -public fun > NDElement.map(transform: C.(T) -> T): NDElement = - context.map(unwrap(), transform).wrap() - -/** - * Element by element application of any operation on elements to the whole [NDElement] - */ -public operator fun > Function1.invoke(ndElement: NDElement): NDElement = - ndElement.map { value -> this@invoke(value) } - -/* plus and minus */ - -/** - * Summation operation for [NDElement] and single element - */ -public operator fun , N : NDStructure> NDElement.plus(arg: T): NDElement = - map { value -> arg + value } - -/** - * Subtraction operation between [NDElement] and single element - */ -public operator fun , N : NDStructure> NDElement.minus(arg: T): NDElement = - map { value -> arg - value } - -/* prod and div */ - -/** - * Product operation for [NDElement] and single element - */ -public operator fun , N : NDStructure> NDElement.times(arg: T): NDElement = - map { value -> arg * value } - -/** - * Division operation between [NDElement] and single element - */ -public operator fun , N : NDStructure> NDElement.div(arg: T): NDElement = - map { value -> arg / value } - -// /** -// * Reverse sum operation -// */ -// operator fun T.plus(arg: NDStructure): NDElement = produce { index -> -// field.run { this@plus + arg[index] } -// } -// -// /** -// * Reverse minus operation -// */ -// operator fun T.minus(arg: NDStructure): NDElement = produce { index -> -// field.run { this@minus - arg[index] } -// } -// -// /** -// * Reverse product operation -// */ -// operator fun T.times(arg: NDStructure): NDElement = produce { index -> -// field.run { this@times * arg[index] } -// } -// -// /** -// * Reverse division operation -// */ -// operator fun T.div(arg: NDStructure): NDElement = produce { index -> -// field.run { this@div / arg[index] } -// } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealNDField.kt deleted file mode 100644 index 60e6de440..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealNDField.kt +++ /dev/null @@ -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 - -@OptIn(UnstableKMathAPI::class) -public class RealNDField(override val shape: IntArray) : - BufferedNDField, - ExtendedNDField>, - RingWithNumbers> { - - 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 { - val d = value.toDouble() - return produce { d } - } - - @Suppress("OVERRIDE_BY_INLINE") - override inline fun map( - arg: NDBuffer, - 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, - 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, - b: NDBuffer, - 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.toElement(): FieldElement, *, out BufferedNDField> = - BufferedNDFieldElement(this@RealNDField, buffer) - - override fun power(arg: NDBuffer, pow: Number): RealNDElement = map(arg) { power(it, pow) } - - override fun exp(arg: NDBuffer): RealNDElement = map(arg) { exp(it) } - - override fun ln(arg: NDBuffer): RealNDElement = map(arg) { ln(it) } - - override fun sin(arg: NDBuffer): RealNDElement = map(arg) { sin(it) } - override fun cos(arg: NDBuffer): RealNDElement = map(arg) { cos(it) } - override fun tan(arg: NDBuffer): RealNDElement = map(arg) { tan(it) } - override fun asin(arg: NDBuffer): RealNDElement = map(arg) { asin(it) } - override fun acos(arg: NDBuffer): RealNDElement = map(arg) { acos(it) } - override fun atan(arg: NDBuffer): RealNDElement = map(arg) { atan(it) } - - override fun sinh(arg: NDBuffer): RealNDElement = map(arg) { sinh(it) } - override fun cosh(arg: NDBuffer): RealNDElement = map(arg) { cosh(it) } - override fun tanh(arg: NDBuffer): RealNDElement = map(arg) { tanh(it) } - override fun asinh(arg: NDBuffer): RealNDElement = map(arg) { asinh(it) } - override fun acosh(arg: NDBuffer): RealNDElement = map(arg) { acosh(it) } - override fun atanh(arg: NDBuffer): RealNDElement = map(arg) { atanh(it) } -} - - -/** - * Fast element production using function inlining - */ -public inline fun BufferedNDField.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.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 RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R = NDField.real(*shape).run(action) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortNDRing.kt deleted file mode 100644 index 3b506a26a..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortNDRing.kt +++ /dev/null @@ -1,93 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.RingElement -import kscience.kmath.operations.ShortRing - -public typealias ShortNDElement = BufferedNDRingElement - -public class ShortNDRing(override val shape: IntArray) : - BufferedNDRing { - - 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 = - ShortBuffer(ShortArray(size) { initializer(it) }) - - /** - * Inline transform an NDStructure to - */ - override fun map( - arg: NDBuffer, - 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, - 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, - b: NDBuffer, - 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.toElement(): RingElement, *, out BufferedNDRing> = - BufferedNDRingElement(this@ShortNDRing, buffer) -} - - -/** - * Fast element production using function inlining. - */ -public inline fun BufferedNDRing.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.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() } diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt index d7755dcb5..a0ac92172 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt @@ -1,9 +1,8 @@ package kscience.kmath.linear +import kscience.kmath.nd.NDStructure +import kscience.kmath.nd.as2D 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.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt index 28dfe46ec..b22da96dd 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt @@ -1,6 +1,5 @@ package kscience.kmath.linear -import kscience.kmath.structures.Matrix import kotlin.test.Test import kotlin.test.assertEquals @@ -9,7 +8,7 @@ class RealLUSolverTest { @Test fun testInvertOne() { val matrix = MatrixContext.real.one(2, 2) - val inverted = MatrixContext.real.inverseWithLUP(matrix) + val inverted = MatrixContext.real.inverseWithLup(matrix) assertEquals(matrix, inverted) } @@ -37,7 +36,7 @@ class RealLUSolverTest { 1.0, 3.0 ) - val inverted = MatrixContext.real.inverseWithLUP(matrix) + val inverted = MatrixContext.real.inverseWithLup(matrix) val expected = Matrix.square( 0.375, -0.125, diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt b/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt index 1129a8a36..35d49e29d 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt @@ -1,5 +1,8 @@ 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 kotlin.test.Test import kotlin.test.assertEquals @@ -7,12 +10,12 @@ import kotlin.test.assertEquals internal class NDFieldTest { @Test 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 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) } } diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt index 22a0d3629..4f0c9fc6d 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt @@ -1,8 +1,8 @@ package kscience.kmath.structures +import kscience.kmath.nd.* import kscience.kmath.operations.Norm import kscience.kmath.operations.invoke -import kscience.kmath.structures.NDElement.Companion.real2D import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test @@ -10,25 +10,30 @@ import kotlin.test.assertEquals @Suppress("UNUSED_VARIABLE") class NumberNDFieldTest { - val array1: RealNDElement = real2D(3, 3) { i, j -> (i + j).toDouble() } - val array2: RealNDElement = real2D(3, 3) { i, j -> (i - j).toDouble() } + val algebra = NDAlgebra.real(3,3) + val array1 = algebra.produce { (i, j) -> (i + j).toDouble() } + val array2 = algebra.produce { (i, j) -> (i - j).toDouble() } @Test fun testSum() { - val sum = array1 + array2 - assertEquals(4.0, sum[2, 2]) + algebra { + val sum = array1 + array2 + assertEquals(4.0, sum[2, 2]) + } } @Test fun testProduct() { - val product = array1 * array2 - assertEquals(0.0, product[2, 2]) + algebra { + val product = array1 * array2 + assertEquals(0.0, product[2, 2]) + } } @Test 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 (j in 0..2) { @@ -40,16 +45,20 @@ class NumberNDFieldTest { @Test fun testExternalFunction() { - val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 } - val result = function(array1) + 1.0 - assertEquals(10.0, result[1, 1]) + algebra { + val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 } + val result = function(array1) + 1.0 + assertEquals(10.0, result[1, 1]) + } } @Test fun testLibraryFunction() { - val abs: (Double) -> Double = ::abs - val result = abs(array2) - assertEquals(2.0, result[0, 2]) + algebra { + val abs: (Double) -> Double = ::abs + val result = abs(array2) + assertEquals(2.0, result[0, 2]) + } } @Test @@ -64,6 +73,8 @@ class NumberNDFieldTest { @Test 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) } } + } } } diff --git a/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt b/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt index 7aa746797..3933ef28b 100644 --- a/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt +++ b/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt @@ -2,6 +2,8 @@ package kscience.kmath.structures import kotlinx.coroutines.* import kscience.kmath.coroutines.Math +import kscience.kmath.nd.DefaultStrides +import kscience.kmath.nd.NDStructure public class LazyNDStructure( public val scope: CoroutineScope, diff --git a/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt index 0244eae7f..52443da0e 100644 --- a/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt +++ b/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt @@ -1,9 +1,8 @@ package kscience.kmath.dimensions import kscience.kmath.linear.* +import kscience.kmath.nd.Structure2D import kscience.kmath.operations.invoke -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.Structure2D /** * A matrix with compile-time controlled dimension diff --git a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt index 82a5399fd..3587823a7 100644 --- a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt +++ b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt @@ -2,8 +2,7 @@ package kscience.kmath.ejml import kscience.kmath.linear.* import kscience.kmath.misc.UnstableKMathAPI -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.NDStructure +import kscience.kmath.nd.NDStructure import kscience.kmath.structures.RealBuffer import org.ejml.dense.row.factory.DecompositionFactory_DDRM import org.ejml.simple.SimpleMatrix @@ -16,21 +15,20 @@ import kotlin.reflect.cast * @property origin the underlying [SimpleMatrix]. * @author Iaroslav Postovalov */ -public class EjmlMatrix( - public val origin: SimpleMatrix, -) : Matrix { +public class EjmlMatrix(public val origin: SimpleMatrix) : Matrix { public override val rowNum: Int get() = origin.numRows() - public override val colNum: Int get() = origin.numCols() @UnstableKMathAPI - override fun getFeature(type: KClass): T? = when (type) { + public override fun getFeature(type: KClass): T? = when (type) { InverseMatrixFeature::class -> object : InverseMatrixFeature { override val inverse: Matrix by lazy { EjmlMatrix(origin.invert()) } } + DeterminantFeature::class -> object : DeterminantFeature { override val determinant: Double by lazy(origin::determinant) } + SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { private val svd by lazy { DecompositionFactory_DDRM.svd(origin.numRows(), origin.numCols(), true, true, false) @@ -42,14 +40,19 @@ public class EjmlMatrix( override val v: Matrix by lazy { EjmlMatrix(SimpleMatrix(svd.getV(null, false))) } override val singularValues: Point by lazy { RealBuffer(svd.singularValues) } } + QRDecompositionFeature::class -> object : QRDecompositionFeature { private val qr by lazy { DecompositionFactory_DDRM.qr().apply { decompose(origin.ddrm.copy()) } } - override val q: Matrix by lazy { EjmlMatrix(SimpleMatrix(qr.getQ(null, false))) } - override val r: Matrix by lazy { EjmlMatrix(SimpleMatrix(qr.getR(null, false))) } + override val q: Matrix by lazy { + EjmlMatrix(SimpleMatrix(qr.getQ(null, false))) + OrthogonalFeature + } + + override val r: Matrix by lazy { EjmlMatrix(SimpleMatrix(qr.getR(null, false))) + UFeature } } + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { override val l: Matrix by lazy { val cholesky = @@ -58,6 +61,7 @@ public class EjmlMatrix( EjmlMatrix(SimpleMatrix(cholesky.getT(null))) + LFeature } } + LupDecompositionFeature::class -> object : LupDecompositionFeature { private val lup by lazy { DecompositionFactory_DDRM.lu(origin.numRows(), origin.numCols()).apply { decompose(origin.ddrm.copy()) } @@ -73,8 +77,9 @@ public class EjmlMatrix( override val p: Matrix by lazy { EjmlMatrix(SimpleMatrix(lup.getRowPivot(null))) } } + else -> null - }?.let { type.cast(it) } + }?.let(type::cast) public override operator fun get(i: Int, j: Int): Double = origin[i, j] diff --git a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt index 04796623e..35767c1f1 100644 --- a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt +++ b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt @@ -1,12 +1,8 @@ package kscience.kmath.ejml -import kscience.kmath.linear.InverseMatrixFeature -import kscience.kmath.linear.MatrixContext -import kscience.kmath.linear.Point -import kscience.kmath.linear.origin +import kscience.kmath.linear.* import kscience.kmath.misc.UnstableKMathAPI -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.getFeature +import kscience.kmath.nd.getFeature import org.ejml.simple.SimpleMatrix /** diff --git a/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt b/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt index 455b52d9d..6af70b6f5 100644 --- a/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt +++ b/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt @@ -5,7 +5,7 @@ import kscience.kmath.linear.LupDecompositionFeature import kscience.kmath.linear.MatrixFeature import kscience.kmath.linear.plus 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.simple.SimpleMatrix import kotlin.random.Random diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt index 274030aff..d0e4a7325 100644 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt +++ b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt @@ -1,12 +1,8 @@ package kscience.kmath.real -import kscience.kmath.linear.MatrixContext -import kscience.kmath.linear.VirtualMatrix -import kscience.kmath.linear.inverseWithLUP -import kscience.kmath.linear.real +import kscience.kmath.linear.* import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.structures.Buffer -import kscience.kmath.structures.Matrix import kscience.kmath.structures.RealBuffer import kscience.kmath.structures.asIterable 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.average(): Double = elements().map { (_, value) -> value }.average() -public inline fun RealMatrix.map(transform: (Double) -> Double): RealMatrix = +public inline fun RealMatrix.map(crossinline transform: (Double) -> Double): RealMatrix = MatrixContext.real.produce(rowNum, colNum) { i, j -> transform(get(i, j)) } @@ -152,7 +148,7 @@ public inline fun RealMatrix.map(transform: (Double) -> Double): RealMatrix = /** * Inverse a square real matrix using LUP decomposition */ -public fun RealMatrix.inverseWithLUP(): RealMatrix = MatrixContext.real.inverseWithLUP(this) +public fun RealMatrix.inverseWithLup(): RealMatrix = MatrixContext.real.inverseWithLup(this) //extended operations diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realND.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realND.kt new file mode 100644 index 000000000..f8757b132 --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realND.kt @@ -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.mapInline(crossinline transform: RealField.(Double) -> Double): NDBuffer { + 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.invoke(ndElement: NDBuffer): NDBuffer = + ndElement.mapInline { this@invoke(it) } + +/* plus and minus */ + +/** + * Summation operation for [NDBuffer] and single element + */ +public operator fun NDBuffer.plus(arg: Double): NDBuffer = mapInline { it + arg } + +/** + * Subtraction operation between [NDBuffer] and single element + */ +public operator fun NDBuffer.minus(arg: Double): NDBuffer = mapInline { it - arg } \ No newline at end of file diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt index a89f99b3c..309997ae3 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt @@ -1,8 +1,8 @@ package kaceince.kmath.real +import kscience.kmath.linear.Matrix import kscience.kmath.linear.build import kscience.kmath.real.* -import kscience.kmath.structures.Matrix import kscience.kmath.structures.contentEquals import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt b/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt index 2abb7742c..bdece28e7 100644 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt +++ b/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt @@ -1,7 +1,7 @@ package kscience.kmath.interpolation +import kscience.kmath.nd.Structure2D import kscience.kmath.structures.Buffer -import kscience.kmath.structures.Structure2D public interface XYPointSet { public val size: Int diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 7de21ad89..40196416e 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -1,8 +1,18 @@ plugins { id("ru.mipt.npm.mpp") } -kotlin.sourceSets.commonMain { - dependencies { - api(project(":kmath-core")) - api(project(":kmath-for-real")) +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + } + } + commonTest{ + dependencies{ + implementation(project(":kmath-for-real")) + } } } + +readme { + this.maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE +} diff --git a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt b/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt index f95264ee1..085641106 100644 --- a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt @@ -1,6 +1,8 @@ package kscience.kmath.histogram import kscience.kmath.linear.Point +import kscience.kmath.nd.DefaultStrides +import kscience.kmath.nd.NDStructure import kscience.kmath.operations.SpaceOperations import kscience.kmath.operations.invoke import kscience.kmath.structures.* diff --git a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt index af22afc6b..87a2b3e68 100644 --- a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt @@ -5,7 +5,6 @@ import kscience.kmath.histogram.fill import kscience.kmath.histogram.put import kscience.kmath.real.RealVector import kscience.kmath.real.invoke -import kscience.kmath.structures.Buffer import kotlin.random.Random import kotlin.test.* diff --git a/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt index 2f3855892..d07c2ba01 100644 --- a/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt @@ -1,6 +1,6 @@ package kscience.kmath.histogram -import kscience.kmath.real.RealVector +import kscience.kmath.linear.Point import kscience.kmath.structures.Buffer import kscience.kmath.structures.asBuffer import java.util.* @@ -11,12 +11,12 @@ import kotlin.math.floor public class UnivariateBin( public val position: Double, public val size: Double, - public val counter: LongCounter = LongCounter() + public val counter: LongCounter = LongCounter(), ) : Bin { //TODO add weighting public override val value: Number get() = counter.sum() - public override val center: RealVector get() = doubleArrayOf(position).asBuffer() + public override val center: Point get() = doubleArrayOf(position).asBuffer() public override val dimension: Int get() = 1 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 */ -public class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : - MutableHistogram { +public class UnivariateHistogram private constructor( + private val factory: (Double) -> UnivariateBin, +) : MutableHistogram { private val bins: TreeMap = TreeMap() diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt index db2a44861..b9c95034e 100644 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt +++ b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt @@ -1,55 +1,68 @@ package kscience.kmath.nd4j import kscience.kmath.misc.UnstableKMathAPI +import kscience.kmath.nd.* import kscience.kmath.operations.* -import kscience.kmath.structures.NDAlgebra -import kscience.kmath.structures.NDField -import kscience.kmath.structures.NDRing -import kscience.kmath.structures.NDSpace +import kscience.kmath.structures.* import org.nd4j.linalg.api.ndarray.INDArray 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]. * * @param T the type of ND-structure element. * @param C the type of the element context. */ -public interface Nd4jArrayAlgebra : NDAlgebra> { +public interface Nd4jArrayAlgebra : NDAlgebra { /** * Wraps [INDArray] to [N]. */ public fun INDArray.wrap(): Nd4jArrayStructure + public val NDStructure.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 { val struct = Nd4j.create(*shape)!!.wrap() struct.indicesIterator().forEach { struct[it] = elementContext.initializer(it) } return struct } - public override fun map(arg: Nd4jArrayStructure, transform: C.(T) -> T): Nd4jArrayStructure { - check(arg) - val newStruct = arg.ndArray.dup().wrap() + public override fun NDStructure.map(transform: C.(T) -> T): Nd4jArrayStructure { + val newStruct = ndArray.dup().wrap() newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) } return newStruct } - public override fun mapIndexed( - arg: Nd4jArrayStructure, + public override fun NDStructure.mapIndexed( transform: C.(index: IntArray, T) -> T, ): Nd4jArrayStructure { - check(arg) - val new = Nd4j.create(*shape).wrap() - new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, arg[idx]) } + val new = Nd4j.create(*this@Nd4jArrayAlgebra.shape).wrap() + new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, this[idx]) } return new } public override fun combine( - a: Nd4jArrayStructure, - b: Nd4jArrayStructure, + a: NDStructure, + b: NDStructure, transform: C.(T, T) -> T, ): Nd4jArrayStructure { - check(a, b) val new = Nd4j.create(*shape).wrap() new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(a[idx], b[idx]) } return new @@ -62,38 +75,32 @@ public interface Nd4jArrayAlgebra : NDAlgebra> * @param T the type of the element contained in ND structure. * @param S the type of space of structure elements. */ -public interface Nd4jArraySpace> : NDSpace>, Nd4jArrayAlgebra { +public interface Nd4jArraySpace> : NDSpace, Nd4jArrayAlgebra { public override val zero: Nd4jArrayStructure get() = Nd4j.zeros(*shape).wrap() - public override fun add(a: Nd4jArrayStructure, b: Nd4jArrayStructure): Nd4jArrayStructure { - check(a, b) + public override fun add(a: NDStructure, b: NDStructure): Nd4jArrayStructure { return a.ndArray.add(b.ndArray).wrap() } - public override operator fun Nd4jArrayStructure.minus(b: Nd4jArrayStructure): Nd4jArrayStructure { - check(this, b) + public override operator fun NDStructure.minus(b: NDStructure): Nd4jArrayStructure { return ndArray.sub(b.ndArray).wrap() } - public override operator fun Nd4jArrayStructure.unaryMinus(): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.unaryMinus(): Nd4jArrayStructure { return ndArray.neg().wrap() } - public override fun multiply(a: Nd4jArrayStructure, k: Number): Nd4jArrayStructure { - check(a) + public override fun multiply(a: NDStructure, k: Number): Nd4jArrayStructure { return a.ndArray.mul(k).wrap() } - public override operator fun Nd4jArrayStructure.div(k: Number): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.div(k: Number): Nd4jArrayStructure { return ndArray.div(k).wrap() } - public override operator fun Nd4jArrayStructure.times(k: Number): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.times(k: Number): Nd4jArrayStructure { return ndArray.mul(k).wrap() } } @@ -105,13 +112,12 @@ public interface Nd4jArraySpace> : NDSpace> : NDRing>, Nd4jArraySpace { +public interface Nd4jArrayRing> : NDRing, Nd4jArraySpace { public override val one: Nd4jArrayStructure get() = Nd4j.ones(*shape).wrap() - public override fun multiply(a: Nd4jArrayStructure, b: Nd4jArrayStructure): Nd4jArrayStructure { - check(a, b) + public override fun multiply(a: NDStructure, b: NDStructure): Nd4jArrayStructure { return a.ndArray.mul(b.ndArray).wrap() } // @@ -168,17 +174,12 @@ public interface Nd4jArrayRing> : NDRing> : NDField>, Nd4jArrayRing { +public interface Nd4jArrayField> : NDField, Nd4jArrayRing { - public override fun divide(a: Nd4jArrayStructure, b: Nd4jArrayStructure): Nd4jArrayStructure { - check(a, b) - return a.ndArray.div(b.ndArray).wrap() - } + public override fun divide(a: NDStructure, b: NDStructure): Nd4jArrayStructure = + a.ndArray.div(b.ndArray).wrap() - public override operator fun Number.div(b: Nd4jArrayStructure): Nd4jArrayStructure { - check(b) - return b.ndArray.rdiv(this).wrap() - } + public override operator fun Number.div(b: NDStructure): Nd4jArrayStructure = b.ndArray.rdiv(this).wrap() public companion object { @@ -219,35 +220,29 @@ public class RealNd4jArrayField(public override val shape: IntArray) : Nd4jArray public override val elementContext: RealField get() = RealField - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asRealStructure()) + public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asRealStructure() - public override operator fun Nd4jArrayStructure.div(arg: Double): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.div(arg: Double): Nd4jArrayStructure { return ndArray.div(arg).wrap() } - public override operator fun Nd4jArrayStructure.plus(arg: Double): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.plus(arg: Double): Nd4jArrayStructure { return ndArray.add(arg).wrap() } - public override operator fun Nd4jArrayStructure.minus(arg: Double): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.minus(arg: Double): Nd4jArrayStructure { return ndArray.sub(arg).wrap() } - public override operator fun Nd4jArrayStructure.times(arg: Double): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.times(arg: Double): Nd4jArrayStructure { return ndArray.mul(arg).wrap() } - public override operator fun Double.div(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) + public override operator fun Double.div(arg: NDStructure): Nd4jArrayStructure { return arg.ndArray.rdiv(this).wrap() } - public override operator fun Double.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) + public override operator fun Double.minus(arg: NDStructure): Nd4jArrayStructure { return arg.ndArray.rsub(this).wrap() } } @@ -259,35 +254,29 @@ public class FloatNd4jArrayField(public override val shape: IntArray) : Nd4jArra public override val elementContext: FloatField get() = FloatField - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asFloatStructure()) + public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asFloatStructure() - public override operator fun Nd4jArrayStructure.div(arg: Float): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.div(arg: Float): Nd4jArrayStructure { return ndArray.div(arg).wrap() } - public override operator fun Nd4jArrayStructure.plus(arg: Float): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.plus(arg: Float): Nd4jArrayStructure { return ndArray.add(arg).wrap() } - public override operator fun Nd4jArrayStructure.minus(arg: Float): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.minus(arg: Float): Nd4jArrayStructure { return ndArray.sub(arg).wrap() } - public override operator fun Nd4jArrayStructure.times(arg: Float): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.times(arg: Float): Nd4jArrayStructure { return ndArray.mul(arg).wrap() } - public override operator fun Float.div(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) + public override operator fun Float.div(arg: NDStructure): Nd4jArrayStructure { return arg.ndArray.rdiv(this).wrap() } - public override operator fun Float.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) + public override operator fun Float.minus(arg: NDStructure): Nd4jArrayStructure { return arg.ndArray.rsub(this).wrap() } } @@ -299,25 +288,21 @@ public class IntNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRi public override val elementContext: IntRing get() = IntRing - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asIntStructure()) + public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asIntStructure() - public override operator fun Nd4jArrayStructure.plus(arg: Int): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.plus(arg: Int): Nd4jArrayStructure { return ndArray.add(arg).wrap() } - public override operator fun Nd4jArrayStructure.minus(arg: Int): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.minus(arg: Int): Nd4jArrayStructure { return ndArray.sub(arg).wrap() } - public override operator fun Nd4jArrayStructure.times(arg: Int): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.times(arg: Int): Nd4jArrayStructure { return ndArray.mul(arg).wrap() } - public override operator fun Int.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) + public override operator fun Int.minus(arg: NDStructure): Nd4jArrayStructure { return arg.ndArray.rsub(this).wrap() } } @@ -329,25 +314,21 @@ public class LongNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayR public override val elementContext: LongRing get() = LongRing - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asLongStructure()) + public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asLongStructure() - public override operator fun Nd4jArrayStructure.plus(arg: Long): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.plus(arg: Long): Nd4jArrayStructure { return ndArray.add(arg).wrap() } - public override operator fun Nd4jArrayStructure.minus(arg: Long): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.minus(arg: Long): Nd4jArrayStructure { return ndArray.sub(arg).wrap() } - public override operator fun Nd4jArrayStructure.times(arg: Long): Nd4jArrayStructure { - check(this) + public override operator fun NDStructure.times(arg: Long): Nd4jArrayStructure { return ndArray.mul(arg).wrap() } - public override operator fun Long.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) + public override operator fun Long.minus(arg: NDStructure): Nd4jArrayStructure { return arg.ndArray.rsub(this).wrap() } } diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt index d47a293c3..2a6b56602 100644 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt +++ b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt @@ -1,7 +1,7 @@ package kscience.kmath.nd4j -import kscience.kmath.structures.MutableNDStructure -import kscience.kmath.structures.NDStructure +import kscience.kmath.nd.MutableNDStructure +import kscience.kmath.nd.NDStructure import org.nd4j.linalg.api.ndarray.INDArray /** diff --git a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt index 650d5670c..04959d290 100644 --- a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt +++ b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt @@ -1,7 +1,7 @@ package kscience.kmath.nd4j -import org.nd4j.linalg.factory.Nd4j import kscience.kmath.operations.invoke +import org.nd4j.linalg.factory.Nd4j import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.fail @@ -20,7 +20,7 @@ internal class Nd4jArrayAlgebraTest { @Test fun testMap() { - val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { map(one) { it + it * 2 } } + val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { one.map() { it + it * 2 } } val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure() expected[intArrayOf(0, 0)] = 3 expected[intArrayOf(0, 1)] = 3 diff --git a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt index 7e46211c1..2f1606061 100644 --- a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt +++ b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt @@ -1,6 +1,6 @@ package kscience.kmath.nd4j -import kscience.kmath.structures.get +import kscience.kmath.nd.get import org.nd4j.linalg.factory.Nd4j import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt index 2471362cb..d3e4806b0 100644 --- a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt +++ b/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt @@ -1,10 +1,10 @@ package kscience.kmath.viktor +import kscience.kmath.misc.UnstableKMathAPI +import kscience.kmath.nd.* +import kscience.kmath.operations.ExtendedField import kscience.kmath.operations.RealField -import kscience.kmath.structures.DefaultStrides -import kscience.kmath.structures.MutableNDStructure -import kscience.kmath.structures.NDField -import kscience.kmath.structures.Strides +import kscience.kmath.operations.RingWithNumbers import org.jetbrains.bio.viktor.F64Array @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) +@OptIn(UnstableKMathAPI::class) @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public class ViktorNDField(public override val shape: IntArray) : NDField { +public class ViktorNDField(public override val shape: IntArray) : NDField, + RingWithNumbers>, ExtendedField> { + + public val NDStructure.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 get() = F64Array.full(init = 0.0, shape = shape).asStructure() public override val one: ViktorNDStructure 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 @@ -42,47 +55,67 @@ public class ViktorNDField(public override val shape: IntArray) : NDField Double): ViktorNDStructure = - F64Array(*shape).apply { + public override fun NDStructure.map(transform: RealField.(Double) -> Double): ViktorNDStructure = + F64Array(*this@ViktorNDField.shape).apply { this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(arg[index]), indices = index) + set(value = RealField.transform(this@map[index]), indices = index) } }.asStructure() - public override fun mapIndexed( - arg: ViktorNDStructure, - transform: RealField.(index: IntArray, Double) -> Double - ): ViktorNDStructure = F64Array(*shape).apply { + public override fun NDStructure.mapIndexed( + transform: RealField.(index: IntArray, Double) -> Double, + ): ViktorNDStructure = F64Array(*this@ViktorNDField.shape).apply { this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(index, arg[index]), indices = index) + set(value = RealField.transform(index, this@mapIndexed[index]), indices = index) } }.asStructure() public override fun combine( - a: ViktorNDStructure, - b: ViktorNDStructure, - transform: RealField.(Double, Double) -> Double + a: NDStructure, + b: NDStructure, + transform: RealField.(Double, Double) -> Double, ): ViktorNDStructure = F64Array(*shape).apply { this@ViktorNDField.strides.indices().forEach { index -> set(value = RealField.transform(a[index], b[index]), indices = index) } }.asStructure() - public override fun add(a: ViktorNDStructure, b: ViktorNDStructure): ViktorNDStructure = + public override fun add(a: NDStructure, b: NDStructure): ViktorNDStructure = (a.f64Buffer + b.f64Buffer).asStructure() - public override fun multiply(a: ViktorNDStructure, k: Number): ViktorNDStructure = + public override fun multiply(a: NDStructure, k: Number): ViktorNDStructure = (a.f64Buffer * k.toDouble()).asStructure() - public override inline fun ViktorNDStructure.plus(b: ViktorNDStructure): ViktorNDStructure = + public override inline fun NDStructure.plus(b: NDStructure): ViktorNDStructure = (f64Buffer + b.f64Buffer).asStructure() - public override inline fun ViktorNDStructure.minus(b: ViktorNDStructure): ViktorNDStructure = + public override inline fun NDStructure.minus(b: NDStructure): ViktorNDStructure = (f64Buffer - b.f64Buffer).asStructure() - public override inline fun ViktorNDStructure.times(k: Number): ViktorNDStructure = + public override inline fun NDStructure.times(k: Number): ViktorNDStructure = (f64Buffer * k.toDouble()).asStructure() - public override inline fun ViktorNDStructure.plus(arg: Double): ViktorNDStructure = + public override inline fun NDStructure.plus(arg: Double): ViktorNDStructure = (f64Buffer.plus(arg)).asStructure() -} \ No newline at end of file + + override fun number(value: Number): ViktorNDStructure = + F64Array.full(init = value.toDouble(), shape = shape).asStructure() + + override fun sin(arg: NDStructure): ViktorNDStructure = arg.map { sin(it) } + + override fun cos(arg: NDStructure): ViktorNDStructure = arg.map { cos(it) } + + override fun asin(arg: NDStructure): ViktorNDStructure = arg.map { asin(it) } + + override fun acos(arg: NDStructure): ViktorNDStructure = arg.map { acos(it) } + + override fun atan(arg: NDStructure): ViktorNDStructure = arg.map { atan(it) } + + override fun power(arg: NDStructure, pow: Number): ViktorNDStructure = arg.map { it.pow(pow) } + + override fun exp(arg: NDStructure): ViktorNDStructure = arg.f64Buffer.exp().asStructure() + + override fun ln(arg: NDStructure): ViktorNDStructure = arg.f64Buffer.log().asStructure() +} + +public fun ViktorNDField(vararg shape: Int): ViktorNDField = ViktorNDField(shape) \ No newline at end of file