diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ad244e1..3b1250dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,19 @@ ### Added - Metropolis-Hastings sampler +- Ojalgo `LinearSpace` implementation. ### Changed +- attributes-kt moved to a separate project, and the version used is 0.3.0 +- Kotlin 2.1. Now use cross-compilation to deploy macOS targets. +- Changed `origin` to `cmMatrix` in kmath-commons to avoid property name clash. Expose bidirectional conversion in `CMLinearSpace` ### Deprecated ### Removed ### Fixed -- Fix EJML to properly treat vectors as columns +- (BREAKING CHANGE) Fix EJML to properly treat vectors as columns ### Security diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index 483a71877..e64f65a7d 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -53,7 +53,6 @@ kotlin { implementation(project(":kmath-dimensions")) implementation(project(":kmath-for-real")) implementation(project(":kmath-tensors")) - implementation(project(":kmath-multik")) implementation(libs.multik.default) implementation(spclibs.kotlinx.benchmark.runtime) } @@ -61,13 +60,14 @@ kotlin { val jvmMain by getting { dependencies { - implementation(project(":kmath-commons")) - implementation(project(":kmath-ejml")) - implementation(project(":kmath-nd4j")) - implementation(project(":kmath-kotlingrad")) - implementation(project(":kmath-viktor")) -// implementation(project(":kmath-jafama")) + implementation(projects.kmathCommons) + implementation(projects.kmathEjml) + implementation(projects.kmathNd4j) + implementation(projects.kmathKotlingrad) + implementation(projects.kmathViktor) + implementation(projects.kmathOjalgo) implementation(projects.kmath.kmathTensorflow) + implementation(projects.kmathMultik) implementation("org.tensorflow:tensorflow-core-platform:0.4.0") implementation("org.nd4j:nd4j-native:1.0.0-M1") // uncomment if your system supports AVX2 diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt index a157a67b2..0a3cddc9a 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt @@ -10,10 +10,13 @@ import kotlinx.benchmark.Blackhole import kotlinx.benchmark.Scope import kotlinx.benchmark.State import space.kscience.kmath.commons.linear.CMLinearSpace +import space.kscience.kmath.commons.linear.CMLinearSpace.dot import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM import space.kscience.kmath.linear.Float64ParallelLinearSpace import space.kscience.kmath.linear.invoke import space.kscience.kmath.linear.linearSpace +import space.kscience.kmath.ojalgo.Ojalgo +import space.kscience.kmath.ojalgo.linearSpace import space.kscience.kmath.operations.Float64Field import space.kscience.kmath.tensorflow.produceWithTF import space.kscience.kmath.tensors.core.tensorAlgebra @@ -70,6 +73,11 @@ internal class DotBenchmark { blackhole.consume(matrix1 dot matrix2) } + @Benchmark + fun ojalgoDot(blackhole: Blackhole) = Ojalgo.R064.linearSpace { + blackhole.consume(matrix1 dot matrix2) + } + @Benchmark fun multikDot(blackhole: Blackhole) = with(multikAlgebra) { blackhole.consume(matrix1 dot matrix2) diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt index 1a9e09013..e67b1e3be 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt @@ -12,10 +12,9 @@ import kotlinx.benchmark.State import space.kscience.kmath.commons.linear.CMLinearSpace import space.kscience.kmath.commons.linear.lupSolver import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM -import space.kscience.kmath.linear.invoke -import space.kscience.kmath.linear.linearSpace -import space.kscience.kmath.linear.lupSolver -import space.kscience.kmath.linear.parallel +import space.kscience.kmath.linear.* +import space.kscience.kmath.ojalgo.Ojalgo +import space.kscience.kmath.ojalgo.linearSpace import space.kscience.kmath.operations.algebra import kotlin.random.Random @@ -48,10 +47,14 @@ internal class MatrixInverseBenchmark { blackhole.consume(lupSolver().inverse(matrix)) } - @Benchmark fun ejmlInverse(blackhole: Blackhole) = EjmlLinearSpaceDDRM { - blackhole.consume(matrix.toEjml().inverted()) + blackhole.consume(matrix.inverted()) + } + + @Benchmark + fun ojalgoInverse(blackhole: Blackhole) = Ojalgo.R064.linearSpace { + blackhole.consume(matrix.getOrComputeAttribute(Inverted)) } } diff --git a/build.gradle.kts b/build.gradle.kts index b5e9a76bf..3bbd99f8b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,8 +6,6 @@ plugins { alias(spclibs.plugins.kotlinx.kover) } -val attributesVersion by extra("0.2.0") - allprojects { repositories { maven("https://repo.kotlin.link") @@ -72,7 +70,7 @@ ksciencePublish { useSPCTeam() } repository("spc", "https://maven.sciprog.center/kscience") - sonatype("https://oss.sonatype.org") + central() } apiValidation.nonPublicMarkers.add("space.kscience.kmath.UnstableKMathAPI") diff --git a/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt b/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt index 1e7211757..9a4049c6e 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.linear +import space.kscience.kmath.PerformancePitfall import space.kscience.kmath.commons.linear.CMLinearSpace import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM import space.kscience.kmath.nd.StructureND @@ -12,6 +13,7 @@ import space.kscience.kmath.operations.algebra import space.kscience.kmath.structures.Float64 import kotlin.random.Random +@OptIn(PerformancePitfall::class) fun main() { val dim = 46 @@ -21,7 +23,7 @@ fun main() { listOf(CMLinearSpace, EjmlLinearSpaceDDRM).forEach { algebra -> with(algebra) { - //create a simmetric matrix + //create a symmetric matrix val matrix = buildMatrix(dim, dim) { row, col -> if (row >= col) u[row, col] else u[col, row] } diff --git a/gradle.properties b/gradle.properties index 0741d89af..463d9c8e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.workers.max=4 kotlin.code.style=official kotlin.mpp.stability.nowarn=true kotlin.native.ignoreDisabledTargets=true -org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled kotlin.native.enableKlibsCrossCompilation=true -toolsVersion=0.16.0-kotlin-2.1.0 \ No newline at end of file +toolsVersion=0.16.1-kotlin-2.1.0 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 180778767..a9026d45b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,9 @@ commons-rng = "1.6" multik = "0.2.3" [libraries] +attributes = "space.kscience:attributes-kt:0.3.0" + +commons-math = "org.apache.commons:commons-math3:3.6.1" commons-rng-simple = { module = "org.apache.commons:commons-rng-simple", version.ref = "commons-rng" } commons-rng-sampling = { module = "org.apache.commons:commons-rng-sampling", version.ref = "commons-rng" } @@ -11,4 +14,6 @@ commons-rng-sampling = { module = "org.apache.commons:commons-rng-sampling", ver multik-core = { module = "org.jetbrains.kotlinx:multik-core", version.ref = "multik" } multik-default = { module = "org.jetbrains.kotlinx:multik-default", version.ref = "multik" } +ojalgo = "org.ojalgo:ojalgo:55.1.0" + [plugins] \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02ca..81aa1c044 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt index dab84e94f..4cd1c8c3d 100644 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt +++ b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt @@ -22,7 +22,7 @@ import space.kscience.kmath.wasm.compile as wasmCompile import space.kscience.kmath.wasm.compileToExpression as wasmCompileToExpression @OptIn(UnstableKMathAPI::class) -private object WasmCompilerTestContext : CompilerTestContext { +internal object WasmCompilerTestContext : CompilerTestContext { override fun MST.compileToExpression(algebra: Int32Ring): Expression = wasmCompileToExpression(algebra) override fun MST.compile(algebra: Int32Ring, arguments: Map): Int = wasmCompile(algebra, arguments) override fun MST.compileToExpression(algebra: Float64Field): Expression = wasmCompileToExpression(algebra) @@ -31,7 +31,7 @@ private object WasmCompilerTestContext : CompilerTestContext { wasmCompile(algebra, arguments) } -private object ESTreeCompilerTestContext : CompilerTestContext { +internal object ESTreeCompilerTestContext : CompilerTestContext { override fun MST.compileToExpression(algebra: Int32Ring): Expression = estreeCompileToExpression(algebra) override fun MST.compile(algebra: Int32Ring, arguments: Map): Int = estreeCompile(algebra, arguments) override fun MST.compileToExpression(algebra: Float64Field): Expression = estreeCompileToExpression(algebra) diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 5c2c4a304..4cefc32c8 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -13,7 +13,7 @@ kscience { api(projects.kmathOptimization) api(projects.kmathStat) api(projects.kmathFunctions) - api("org.apache.commons:commons-math3:3.6.1") + api(libs.commons.math) } } diff --git a/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMLinearSpace.kt similarity index 64% rename from kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt rename to kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMLinearSpace.kt index 416ad0838..468b4f9e7 100644 --- a/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt +++ b/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMLinearSpace.kt @@ -6,8 +6,6 @@ package space.kscience.kmath.commons.linear import org.apache.commons.math3.linear.* -import org.apache.commons.math3.linear.LUDecomposition -import org.apache.commons.math3.linear.SingularValueDecomposition import space.kscience.attributes.SafeType import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.linear.* @@ -23,27 +21,26 @@ import space.kscience.kmath.structures.Float64 import space.kscience.kmath.structures.IntBuffer import space.kscience.kmath.structures.asBuffer -public class CMMatrix(public val origin: RealMatrix) : Matrix { +@JvmInline +public value class CMMatrix(public val cmMatrix: RealMatrix) : Matrix { - override val rowNum: Int get() = origin.rowDimension - override val colNum: Int get() = origin.columnDimension + override val rowNum: Int get() = cmMatrix.rowDimension + override val colNum: Int get() = cmMatrix.columnDimension - override operator fun get(i: Int, j: Int): Double = origin.getEntry(i, j) + override operator fun get(i: Int, j: Int): Double = cmMatrix.getEntry(i, j) } @JvmInline -public value class CMVector(public val origin: RealVector) : Point { - override val size: Int get() = origin.dimension +public value class CMVector(public val cmVector: RealVector) : Point { + override val size: Int get() = cmVector.dimension - override operator fun get(index: Int): Double = origin.getEntry(index) + override operator fun get(index: Int): Double = cmVector.getEntry(index) - override operator fun iterator(): Iterator = origin.toArray().iterator() + override operator fun iterator(): Iterator = cmVector.toArray().iterator() override fun toString(): String = Buffer.toString(this) } -public fun RealVector.toPoint(): CMVector = CMVector(this) - public object CMLinearSpace : LinearSpace { override val elementAlgebra: Float64Field get() = Float64Field @@ -59,52 +56,52 @@ public object CMLinearSpace : LinearSpace { } @OptIn(UnstableKMathAPI::class) - public fun Matrix.toCM(): CMMatrix = when (val matrix = origin) { - is CMMatrix -> matrix + public fun Matrix.toCM(): RealMatrix = when (val matrix = origin) { + is CMMatrix -> matrix.cmMatrix else -> { //TODO add feature analysis val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } } - Array2DRowRealMatrix(array).wrap() + Array2DRowRealMatrix(array) } } - public fun Point.toCM(): CMVector = if (this is CMVector) this else { - val array = DoubleArray(size) { this[it] } - ArrayRealVector(array).wrap() + public fun Point.toCM(): RealVector = if (this is CMVector) cmVector else { + val array = DoubleArray(size) { get(it) } + ArrayRealVector(array) } - internal fun RealMatrix.wrap(): CMMatrix = CMMatrix(this) - internal fun RealVector.wrap(): CMVector = CMVector(this) + public fun RealMatrix.asMatrix(): CMMatrix = CMMatrix(this) + public fun RealVector.asVector(): CMVector = CMVector(this) override fun buildVector(size: Int, initializer: Float64Field.(Int) -> Double): Point = - ArrayRealVector(DoubleArray(size) { Float64Field.initializer(it) }).wrap() + ArrayRealVector(DoubleArray(size) { Float64Field.initializer(it) }).asVector() override fun Matrix.plus(other: Matrix): CMMatrix = - toCM().origin.add(other.toCM().origin).wrap() + toCM().add(other.toCM()).asMatrix() override fun Point.plus(other: Point): CMVector = - toCM().origin.add(other.toCM().origin).wrap() + toCM().add(other.toCM()).asVector() override fun Point.minus(other: Point): CMVector = - toCM().origin.subtract(other.toCM().origin).wrap() + toCM().subtract(other.toCM()).asVector() override fun Matrix.dot(other: Matrix): CMMatrix = - toCM().origin.multiply(other.toCM().origin).wrap() + toCM().multiply(other.toCM()).asMatrix() override fun Matrix.dot(vector: Point): CMVector = - toCM().origin.preMultiply(vector.toCM().origin).wrap() + toCM().preMultiply(vector.toCM()).asVector() override operator fun Matrix.minus(other: Matrix): CMMatrix = - toCM().origin.subtract(other.toCM().origin).wrap() + toCM().subtract(other.toCM()).asMatrix() override operator fun Matrix.times(value: Double): CMMatrix = - toCM().origin.scalarMultiply(value).wrap() + toCM().scalarMultiply(value).asMatrix() override fun Double.times(m: Matrix): CMMatrix = m * this override fun Point.times(value: Double): CMVector = - toCM().origin.mapMultiply(value).wrap() + toCM().mapMultiply(value).asVector() override fun Double.times(v: Point): CMVector = v * this @@ -112,36 +109,38 @@ public object CMLinearSpace : LinearSpace { @OptIn(UnstableKMathAPI::class) override fun > computeAttribute(structure: Structure2D, attribute: A): V? { - val origin = structure.toCM().origin + val origin = structure.toCM() val raw: Any? = when (attribute) { IsDiagonal -> if (origin is DiagonalMatrix) Unit else null - Determinant -> LUDecomposition(origin).determinant + Determinant -> org.apache.commons.math3.linear.LUDecomposition(origin).determinant + + Inverted -> org.apache.commons.math3.linear.LUDecomposition(origin).solver.inverse.asMatrix() LUP -> object : LupDecomposition { - val lup by lazy { LUDecomposition(origin) } + val lup by lazy { org.apache.commons.math3.linear.LUDecomposition(origin) } override val pivot: IntBuffer get() = lup.pivot.asBuffer() - override val l: Matrix get() = lup.l.wrap() - override val u: Matrix get() = lup.u.wrap() + override val l: Matrix get() = lup.l.asMatrix().withAttribute(LowerTriangular) + override val u: Matrix get() = lup.u.asMatrix().withAttribute(UpperTriangular) } Cholesky -> object : CholeskyDecomposition { val cmCholesky by lazy { org.apache.commons.math3.linear.CholeskyDecomposition(origin) } - override val l: Matrix get() = cmCholesky.l.wrap() + override val l: Matrix get() = cmCholesky.l.asMatrix() } QR -> object : QRDecomposition { val cmQr by lazy { org.apache.commons.math3.linear.QRDecomposition(origin) } - override val q: Matrix get() = cmQr.q.wrap().withAttribute(OrthogonalAttribute) - override val r: Matrix get() = cmQr.r.wrap().withAttribute(UpperTriangular) + override val q: Matrix get() = cmQr.q.asMatrix().withAttribute(OrthogonalAttribute) + override val r: Matrix get() = cmQr.r.asMatrix().withAttribute(UpperTriangular) } SVD -> object : space.kscience.kmath.linear.SingularValueDecomposition { - val cmSvd by lazy { SingularValueDecomposition(origin) } + val cmSvd by lazy { org.apache.commons.math3.linear.SingularValueDecomposition(origin) } - override val u: Matrix get() = cmSvd.u.wrap() - override val s: Matrix get() = cmSvd.s.wrap() - override val v: Matrix get() = cmSvd.v.wrap() + override val u: Matrix get() = cmSvd.u.asMatrix() + override val s: Matrix get() = cmSvd.s.asMatrix() + override val v: Matrix get() = cmSvd.v.asMatrix() override val singularValues: Point get() = cmSvd.singularValues.asBuffer() } @@ -149,8 +148,8 @@ public object CMLinearSpace : LinearSpace { EIG -> object : EigenDecomposition { val cmEigen by lazy { org.apache.commons.math3.linear.EigenDecomposition(origin) } - override val v: Matrix get() = cmEigen.v.wrap() - override val d: Matrix get() = cmEigen.d.wrap() + override val v: Matrix get() = cmEigen.v.asMatrix() + override val d: Matrix get() = cmEigen.d.asMatrix() } else -> null @@ -161,8 +160,8 @@ public object CMLinearSpace : LinearSpace { } -public operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(origin.add(other.origin)) +public operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(cmMatrix.add(other.cmMatrix)) -public operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(origin.subtract(other.origin)) +public operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(cmMatrix.subtract(other.cmMatrix)) -public infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(origin.multiply(other.origin)) +public infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(cmMatrix.multiply(other.cmMatrix)) diff --git a/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt b/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt index ec12de1f2..cf6d1f6ac 100644 --- a/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt +++ b/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt @@ -19,43 +19,44 @@ public enum class CMDecomposition { CHOLESKY } -private fun CMLinearSpace.solver( +private fun CMLinearSpace.cmSolver( a: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP, ): DecompositionSolver = when (decomposition) { - CMDecomposition.LUP -> LUDecomposition(a.toCM().origin).solver - CMDecomposition.RRQR -> RRQRDecomposition(a.toCM().origin).solver - CMDecomposition.QR -> QRDecomposition(a.toCM().origin).solver - CMDecomposition.EIGEN -> EigenDecomposition(a.toCM().origin).solver - CMDecomposition.CHOLESKY -> CholeskyDecomposition(a.toCM().origin).solver + CMDecomposition.LUP -> LUDecomposition(a.toCM()).solver + CMDecomposition.RRQR -> RRQRDecomposition(a.toCM()).solver + CMDecomposition.QR -> QRDecomposition(a.toCM()).solver + CMDecomposition.EIGEN -> EigenDecomposition(a.toCM()).solver + CMDecomposition.CHOLESKY -> CholeskyDecomposition(a.toCM()).solver } public fun CMLinearSpace.solve( a: Matrix, b: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP, -): CMMatrix = solver(a, decomposition).solve(b.toCM().origin).wrap() +): CMMatrix = cmSolver(a, decomposition).solve(b.toCM()).asMatrix() public fun CMLinearSpace.solve( a: Matrix, b: Point, decomposition: CMDecomposition = CMDecomposition.LUP, -): CMVector = solver(a, decomposition).solve(b.toCM().origin).toPoint() +): CMVector = cmSolver(a, decomposition).solve(b.toCM()).asVector() public fun CMLinearSpace.inverse( a: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP, -): CMMatrix = solver(a, decomposition).inverse.wrap() +): CMMatrix = cmSolver(a, decomposition).inverse.asMatrix() -public fun CMLinearSpace.solver(decomposition: CMDecomposition): LinearSolver = object : LinearSolver { - override fun solve(a: Matrix, b: Matrix): Matrix = - solver(a, decomposition).solve(b.toCM().origin).wrap() +public fun CMLinearSpace.solver(decomposition: CMDecomposition): LinearSolver = + object : LinearSolver { + override fun solve(a: Matrix, b: Matrix): Matrix = + cmSolver(a, decomposition).solve(b.toCM()).asMatrix() - override fun solve(a: Matrix, b: Point): Point = - solver(a, decomposition).solve(b.toCM().origin).toPoint() + override fun solve(a: Matrix, b: Point): Point = + cmSolver(a, decomposition).solve(b.toCM()).asVector() - override fun inverse(matrix: Matrix): Matrix = solver(matrix, decomposition).inverse.wrap() -} + override fun inverse(matrix: Matrix): Matrix = cmSolver(matrix, decomposition).inverse.asMatrix() + } public fun CMLinearSpace.lupSolver(): LinearSolver = solver((CMDecomposition.LUP)) \ No newline at end of file diff --git a/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt b/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt index f0c9b0fb0..6e85fae3f 100644 --- a/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt +++ b/kmath-commons/src/jvmMain/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt @@ -34,7 +34,7 @@ public object CMOptimizerEngine : OptimizationAttribute<() -> MultivariateOptimi * Specify a Commons-maths optimization engine */ public fun AttributesBuilder>.cmEngine(optimizerBuilder: () -> MultivariateOptimizer) { - set(CMOptimizerEngine, optimizerBuilder) + CMOptimizerEngine(optimizerBuilder) } public object CMOptimizerData : SetAttribute OptimizationData> diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt index 4082b6f4f..1facd5953 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt @@ -6,7 +6,7 @@ package space.kscience.kmath.linear /** - * A group of methods to solve for *X* in equation *X = A−1 · B*, where *A* and *B* are + * A group of methods to solve for $X$ in equation $X = A^{-1} \cdot B$, where $A$ and $B$ are * matrices or vectors. * * @param T the type of items. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt index a5987bb18..ef447ace0 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt @@ -8,6 +8,7 @@ package space.kscience.kmath.linear import space.kscience.attributes.Attribute import space.kscience.attributes.Attributes import space.kscience.attributes.withAttribute +import space.kscience.attributes.withFlag import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.operations.Ring @@ -47,7 +48,7 @@ public fun > Matrix.withAttribute( public fun > Matrix.withAttribute( attribute: A, ): MatrixWrapper = if (this is MatrixWrapper) { - MatrixWrapper(origin, attributes.withAttribute(attribute)) + MatrixWrapper(origin, attributes.withFlag(attribute)) } else { MatrixWrapper(this, Attributes(attribute, Unit)) } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt index fdb7e318b..59ab009ec 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt @@ -15,7 +15,7 @@ import space.kscience.kmath.nd.StructureAttribute * A marker interface for algebras that operate on matrices * @param T type of matrix element */ -public interface MatrixScope : AttributeScope>, WithType +public interface MatrixScope : WithType /** * A marker interface representing some properties of matrices or additional transformations of them. Features are used diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt index 14f2aa7c2..0a5a3642d 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt @@ -117,12 +117,13 @@ internal class MutableBuffer1DWrapper(val buffer: MutableBuffer) : Mutable /** * Represent a [StructureND] as [Structure1D]. Throw error in case of dimension mismatch. */ -public fun StructureND.as1D(): Structure1D = this as? Structure1D ?: if (shape.size == 1) { - when (this) { - is BufferND -> Buffer1DWrapper(this.buffer) - else -> Structure1DWrapper(this) - } -} else error("Can't create 1d-structure from ${shape.size}d-structure") +public fun StructureND.as1D(): Structure1D = + this as? Structure1D ?: if (shape.size == 1) { + when (this) { + is BufferND -> Buffer1DWrapper(this.buffer) + else -> Structure1DWrapper(this) + } + } else error("Can't create 1d-structure from ${shape.size}d-structure") public fun MutableStructureND.as1D(): MutableStructure1D = this as? MutableStructure1D ?: if (shape.size == 1) { @@ -133,13 +134,3 @@ public fun MutableStructureND.as1D(): MutableStructure1D = * Represent this buffer as 1D structure */ public fun Buffer.asND(): Structure1D = Buffer1DWrapper(this) - -/** - * Expose inner buffer of this [Structure1D] if possible - */ -internal fun Structure1D.asND(): Buffer = when { - this is Buffer1DWrapper -> buffer - this is Structure1DWrapper && structure is BufferND -> structure.buffer - else -> this -} - diff --git a/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt index fa6db7817..3d628c030 100644 --- a/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt +++ b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt @@ -229,6 +229,7 @@ public object EjmlLinearSpaceDDRM : EjmlLinearSpace CommonOps_DDRM.det(origin) + SVD -> object : SingularValueDecomposition { val ejmlSvd by lazy { DecompositionFactory_DDRM diff --git a/kmath-jafama/build.gradle.kts b/kmath-jafama/build.gradle.kts deleted file mode 100644 index 0390224ba..000000000 --- a/kmath-jafama/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id("space.kscience.gradle.jvm") -} - -description = "Jafama integration module" - -dependencies { - api(project(":kmath-core")) - api("net.jafama:jafama:2.3.2") -} - -repositories { - mavenCentral() -} - -readme { - maturity = space.kscience.gradle.Maturity.DEPRECATED - propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) - - feature("jafama-double", "src/main/kotlin/space/kscience/kmath/jafama/") { - "Double ExtendedField implementations based on Jafama" - } -} \ No newline at end of file diff --git a/kmath-ojalgo/build.gradle.kts b/kmath-ojalgo/build.gradle.kts new file mode 100644 index 000000000..c8c8a4abb --- /dev/null +++ b/kmath-ojalgo/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("space.kscience.gradle.mpp") +} + +description = "Ojalgo bindings for kmath" + +kscience { + jvm() + jvmMain { + api(projects.kmathCore) +// api(projects.kmathComplex) +// api(projects.kmathCoroutines) +// api(projects.kmathOptimization) +// api(projects.kmathStat) +// api(projects.kmathFunctions) + api(libs.ojalgo) + } +} + +readme { + maturity = space.kscience.gradle.Maturity.PROTOTYPE +} \ No newline at end of file diff --git a/kmath-ojalgo/src/jvmMain/kotlin/space/kscience/kmath/ojalgo/Ojalgo.kt b/kmath-ojalgo/src/jvmMain/kotlin/space/kscience/kmath/ojalgo/Ojalgo.kt new file mode 100644 index 000000000..76f0ba604 --- /dev/null +++ b/kmath-ojalgo/src/jvmMain/kotlin/space/kscience/kmath/ojalgo/Ojalgo.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2018-2025 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ojalgo + +import org.ojalgo.matrix.decomposition.* +import org.ojalgo.matrix.store.PhysicalStore +import org.ojalgo.matrix.store.R064Store +import space.kscience.kmath.operations.Float64Field +import space.kscience.kmath.operations.Ring +import space.kscience.kmath.structures.Float64 + +public class Ojalgo, A : Ring>( + public val elementAlgebra: A, + public val storeFactory: PhysicalStore.Factory, + public val lu: LU.Factory, + public val cholesky: Cholesky.Factory, + public val qr: QR.Factory, + public val svd: SingularValue.Factory, + public val eigen: Eigenvalue.Factory +) { + public companion object { + public val R064: Ojalgo = Ojalgo( + elementAlgebra = Float64Field, + storeFactory = R064Store.FACTORY, + lu = LU.R064, + cholesky = Cholesky.R064, + qr = QR.R064, + svd = SingularValue.R064, + eigen = Eigenvalue.R064 + ) + } +} \ No newline at end of file diff --git a/kmath-ojalgo/src/jvmMain/kotlin/space/kscience/kmath/ojalgo/OjalgoLinearSpace.kt b/kmath-ojalgo/src/jvmMain/kotlin/space/kscience/kmath/ojalgo/OjalgoLinearSpace.kt new file mode 100644 index 000000000..0c287355d --- /dev/null +++ b/kmath-ojalgo/src/jvmMain/kotlin/space/kscience/kmath/ojalgo/OjalgoLinearSpace.kt @@ -0,0 +1,180 @@ +/* + * Copyright 2018-2024 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ojalgo + +import org.ojalgo.matrix.store.MatrixStore +import org.ojalgo.matrix.store.PhysicalStore +import space.kscience.kmath.UnstableKMathAPI +import space.kscience.kmath.linear.* +import space.kscience.kmath.nd.StructureAttribute +import space.kscience.kmath.operations.Ring +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.IntBuffer +import space.kscience.kmath.structures.asBuffer +import space.kscience.kmath.structures.asList + +@JvmInline +public value class OjalgoBuffer>(public val ojalgoMatrix: MatrixStore) : Buffer { + override val size: Int get() = ojalgoMatrix.size() + + override fun get(index: Int): T = ojalgoMatrix.get(index.toLong()) + + override fun toString(): String = ojalgoMatrix.toString() +} + +@JvmInline +public value class OjalgoMatrix>(public val ojalgoVector: MatrixStore) : Matrix { + override val rowNum: Int get() = ojalgoVector.rowDim + + override val colNum: Int get() = ojalgoVector.colDim + + override fun get(i: Int, j: Int): T = ojalgoVector.get(i.toLong(), j.toLong()) +} + + +public class OjalgoLinearSpace, A : Ring>( + public val ojalgo: Ojalgo +) : LinearSpace { + + override val elementAlgebra: A + get() = ojalgo.elementAlgebra + + public fun MatrixStore.asMatrix(): OjalgoMatrix = OjalgoMatrix(this) + + public fun MatrixStore.asVector(): OjalgoBuffer = OjalgoBuffer(this) + + /** + * If this matrix is [OjalgoMatrix] return it without conversion, otherwise create new [PhysicalStore] + */ + @OptIn(UnstableKMathAPI::class) + public fun Matrix.toOjalgo(): MatrixStore = when (val matrix = origin) { + is OjalgoMatrix -> matrix.ojalgoVector + else -> ojalgo.storeFactory.make(rowNum.toLong(), colNum.toLong()).apply { + for (row in 0 until rowNum) { + for (column in 0 until colNum) { + set(row.toLong(), column.toLong(), get(row, column)) + } + } + } + + } + + /** + * If this vector is [OjalgoBuffer] return it without conversion, otherwise create new [PhysicalStore] + */ + public fun Point.toOjalgo(): MatrixStore = + (this as? OjalgoBuffer)?.ojalgoMatrix ?: ojalgo.storeFactory.column(asList()) + + override fun buildMatrix( + rows: Int, + columns: Int, + initializer: A.(Int, Int) -> T + ): Matrix { + val structure: MatrixStore = ojalgo.storeFactory.make(rows.toLong(), columns.toLong()).apply { + for (row in 0 until rows) { + for (column in 0 until columns) { + set(row.toLong(), column.toLong(), elementAlgebra.initializer(row, column)) + } + } + } + return OjalgoMatrix(structure) + } + + override fun buildVector(size: Int, initializer: A.(Int) -> T): Point { + val structure: MatrixStore = ojalgo.storeFactory.column(List(size) { elementAlgebra.initializer(it) }) + return OjalgoBuffer(structure) + } + + @OptIn(UnstableKMathAPI::class) + override fun > computeAttribute( + structure: Matrix, + attribute: A + ): V? { + + val origin = structure.toOjalgo() + + val raw: Any? = when (attribute) { + Determinant -> ojalgo.lu.make(origin).apply { decompose(origin) }.determinant + + Inverted -> ojalgo.lu.make().apply { decompose(origin) }.inverse.asMatrix() + + LUP -> object : LupDecomposition { + val lup by lazy { + ojalgo.lu.make(origin).apply { decompose(origin) } + } + override val pivot: IntBuffer get() = lup.pivotOrder.asBuffer() + override val l: Matrix get() = lup.l.asMatrix().withAttribute(LowerTriangular) + override val u: Matrix get() = lup.u.asMatrix().withAttribute(UpperTriangular) + } + + Cholesky -> object : CholeskyDecomposition { + val cholesky by lazy { + ojalgo.cholesky.make(origin).apply { decompose(origin) } + } + override val l: Matrix get() = cholesky.l.asMatrix() + } + + QR -> object : QRDecomposition { + val qr by lazy { + ojalgo.qr.make(origin).apply { decompose(origin) } + } + override val q: Matrix get() = qr.q.asMatrix().withAttribute(OrthogonalAttribute) + override val r: Matrix get() = qr.r.asMatrix().withAttribute(UpperTriangular) + } + + SVD -> object : SingularValueDecomposition { + val svd by lazy { + ojalgo.svd.make(origin).apply { decompose(origin) } + } + + override val u: Matrix get() = svd.u.asMatrix() + override val s: Matrix get() = ojalgo.storeFactory.makeDiagonal(svd.singularValues).get().asMatrix() + override val v: Matrix get() = svd.v.asMatrix() + + override val singularValues: Point + get() = ojalgo.storeFactory.asFactory1D().make(svd.singularValues).asList().asBuffer() + + } + + EIG -> object : EigenDecomposition { + val eigen by lazy { + ojalgo.eigen.make(origin).apply { decompose(origin) } + } + + override val v: Matrix get() = eigen.v.asMatrix() + override val d: Matrix get() = eigen.d.asMatrix() + } + + else -> null + } + @Suppress("UNCHECKED_CAST") + return raw as V? + } + + override fun Matrix.times(value: T): OjalgoMatrix = toOjalgo().multiply(value).asMatrix() + + override fun Matrix.dot(vector: Point): OjalgoBuffer = toOjalgo().multiply(vector.toOjalgo()).asVector() + + override fun Matrix.dot(other: Matrix): OjalgoMatrix = toOjalgo().multiply(other.toOjalgo()).asMatrix() + + override fun Point.times(value: T): OjalgoBuffer = toOjalgo().multiply(value).asVector() + + override fun Point.minus(other: Point): OjalgoBuffer = toOjalgo().subtract(other.toOjalgo()).asVector() + + override fun Matrix.minus(other: Matrix): OjalgoMatrix = toOjalgo().subtract(other.toOjalgo()).asMatrix() + + override fun Point.plus(other: Point): OjalgoBuffer = toOjalgo().subtract(other.toOjalgo()).asVector() + + override fun Matrix.plus(other: Matrix): OjalgoMatrix = toOjalgo().add(other.toOjalgo()).asMatrix() + + override fun Point.unaryMinus(): OjalgoBuffer = toOjalgo().negate().asVector() + + override fun Matrix.unaryMinus(): OjalgoMatrix = toOjalgo().negate().asMatrix() + +} + +public val , A : Ring> Ojalgo.linearSpace: OjalgoLinearSpace + get() = OjalgoLinearSpace(this) \ No newline at end of file diff --git a/kmath-ojalgo/src/jvmTest/kotlin/space/kscience/kmath/ojalgo/OjalgoMatrixTest.kt b/kmath-ojalgo/src/jvmTest/kotlin/space/kscience/kmath/ojalgo/OjalgoMatrixTest.kt new file mode 100644 index 000000000..e310001ad --- /dev/null +++ b/kmath-ojalgo/src/jvmTest/kotlin/space/kscience/kmath/ojalgo/OjalgoMatrixTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2018-2025 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ojalgo + +import org.junit.jupiter.api.Test +import space.kscience.kmath.PerformancePitfall +import space.kscience.kmath.UnstableKMathAPI +import space.kscience.kmath.linear.* +import space.kscience.kmath.nd.StructureND +import space.kscience.kmath.structures.Float64 +import kotlin.test.assertEquals +import kotlin.test.assertTrue + + +@UnstableKMathAPI +@OptIn(PerformancePitfall::class) +@Suppress("UNUSED_VARIABLE") +class OjalgoMatrixTest { + + @Test + fun testTranspose() = with(Ojalgo.Companion.R064.linearSpace) { + val matrix = one(3, 3) + val transposed = matrix.transposed() + assertTrue { StructureND.Companion.contentEquals(matrix, transposed) } + } + + @Test + fun testBuilder() = Ojalgo.Companion.R064.linearSpace { + val matrix = matrix(2, 3)( + 1.0, 0.0, 0.0, + 0.0, 1.0, 2.0 + ) + + assertEquals(2.0, matrix[1, 2]) + } + + @Test + fun testMatrixExtension() = with(Ojalgo.Companion.R064.linearSpace) { + val transitionMatrix: Matrix = VirtualMatrix(6, 6) { row, col -> + when { + col == 0 -> .50 + row + 1 == col -> .50 + row == 5 && col == 5 -> 1.0 + else -> 0.0 + } + } + + infix fun Matrix.pow(power: Int): Matrix { + var res = this + repeat(power - 1) { + res = res dot this@pow + } + return res + } + + val toTenthPower = transitionMatrix pow 10 + } + + @Test + fun test2DDot() = with(Ojalgo.Companion.R064.linearSpace) { + val firstMatrix = buildMatrix(2, 3) { i, j -> (i + j).toDouble() } + val secondMatrix = buildMatrix(3, 2) { i, j -> (i + j).toDouble() } + +// val firstMatrix = produce(2, 3) { i, j -> (i + j).toDouble() } +// val secondMatrix = produce(3, 2) { i, j -> (i + j).toDouble() } + val result = firstMatrix dot secondMatrix + assertEquals(2, result.rowNum) + assertEquals(2, result.colNum) + assertEquals(8.0, result[0, 1]) + assertEquals(8.0, result[1, 0]) + assertEquals(14.0, result[1, 1]) + } +} \ No newline at end of file diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index 263576d77..e881c4c7c 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -12,7 +12,7 @@ import space.kscience.kmath.expressions.Symbol public class OptimizationValue(type: SafeType) : PolymorphicAttribute(type) public inline fun AttributesBuilder>.value(value: T) { - set(OptimizationValue(safeTypeOf()), value) + put(OptimizationValue(safeTypeOf()), value) } public enum class OptimizationDirection { diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt index 4c0e4038e..d0567a505 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -24,7 +24,7 @@ public val OptimizationProblem.startPoint: Map get() = attributes[OptimizationStartPoint()] ?: error("Starting point not defined in $this") public fun AttributesBuilder>.startAt(startingPoint: Map) { - set(OptimizationStartPoint(), startingPoint) + put(OptimizationStartPoint(), startingPoint) } @@ -35,7 +35,7 @@ public class OptimizationCovariance : OptimizationAttribute>, PolymorphicAttribute>(safeTypeOf()) public fun AttributesBuilder>.covariance(covariance: NamedMatrix) { - set(OptimizationCovariance(), covariance) + put(OptimizationCovariance(), covariance) } @@ -43,7 +43,7 @@ public class OptimizationResult() : OptimizationAttribute>, PolymorphicAttribute>(safeTypeOf()) public fun AttributesBuilder>.result(result: Map) { - set(OptimizationResult(), result) + put(OptimizationResult(), result) } public val OptimizationProblem.resultOrNull: Map? get() = attributes[OptimizationResult()] diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index d27783926..d45dcc35a 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -125,9 +125,9 @@ public suspend fun XYColumnarData.fitWith( this, modelExpression, attributes.modified { - set(OptimizationStartPoint(), startingPoint) - if (!hasAny()) { - set(OptimizationLog, Loggable.console) + put(OptimizationStartPoint(), startingPoint) + if (!attributes.hasAny()) { + put(OptimizationLog, Loggable.console) } }, pointToCurveDistance, diff --git a/settings.gradle.kts b/settings.gradle.kts index 2b0faafca..950505c0f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,7 +38,6 @@ dependencyResolutionManagement { include( ":test-utils", - ":attributes-kt", ":kmath-memory", ":kmath-complex", ":kmath-core", @@ -61,6 +60,7 @@ include( ":kmath-tensors", ":kmath-jupyter", ":kmath-symja", + ":kmath-ojalgo", ":examples", ":benchmarks", )