Add sparse matrix builder

This commit is contained in:
Alexander Nozik 2025-01-26 22:17:36 +03:00
parent 676cf5ff3b
commit c70a7c5128
22 changed files with 316 additions and 94 deletions
CHANGELOG.md
benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks
build.gradle.kts
examples/src/main/kotlin/space/kscience/kmath/operations
kmath-core/src
commonMain/kotlin/space/kscience/kmath
commonTest/kotlin/space/kscience/kmath/linear
jvmTest/kotlin/space/kscience/kmath/linear
kmath-for-real/src
commonMain/kotlin/space/kscience/kmath/real
commonTest/kotlin/space/kscience/kmath/real
kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/euclidean3d
kmath-ojalgo
build.gradle.kts
src/jvmTest/kotlin/space/kscience/kmath/ojalgo
kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization

@ -4,6 +4,7 @@
### Added ### Added
- Convenient matrix builders for rows, columns, vstacks and hstacks - Convenient matrix builders for rows, columns, vstacks and hstacks
- Spreadsheet matrix builder
### Changed ### Changed

@ -9,8 +9,8 @@ import kotlinx.benchmark.Benchmark
import kotlinx.benchmark.Blackhole import kotlinx.benchmark.Blackhole
import kotlinx.benchmark.Scope import kotlinx.benchmark.Scope
import kotlinx.benchmark.State import kotlinx.benchmark.State
import space.kscience.kmath.linear.MatrixBuilder
import space.kscience.kmath.linear.linearSpace import space.kscience.kmath.linear.linearSpace
import space.kscience.kmath.linear.matrix
import space.kscience.kmath.linear.symmetric import space.kscience.kmath.linear.symmetric
import space.kscience.kmath.operations.Float64Field import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.tensors.core.symEigJacobi import space.kscience.kmath.tensors.core.symEigJacobi
@ -24,7 +24,7 @@ internal class TensorAlgebraBenchmark {
private val random = Random(12224) private val random = Random(12224)
private const val dim = 30 private const val dim = 30
private val matrix = Float64Field.linearSpace.matrix(dim, dim).symmetric { _, _ -> random.nextDouble() } private val matrix = Float64Field.linearSpace.MatrixBuilder(dim, dim).symmetric { _, _ -> random.nextDouble() }
} }
@Benchmark @Benchmark

@ -14,7 +14,7 @@ allprojects {
} }
group = "space.kscience" group = "space.kscience"
version = "0.4.1" version = "0.4.2-dev"
} }
dependencies{ dependencies{

@ -6,22 +6,21 @@
package space.kscience.kmath.operations package space.kscience.kmath.operations
import space.kscience.kmath.commons.linear.CMLinearSpace import space.kscience.kmath.commons.linear.CMLinearSpace
import space.kscience.kmath.linear.matrix import space.kscience.kmath.linear.MatrixBuilder
import space.kscience.kmath.linear.fill
import space.kscience.kmath.nd.Float64BufferND import space.kscience.kmath.nd.Float64BufferND
import space.kscience.kmath.nd.Structure2D import space.kscience.kmath.nd.Structure2D
import space.kscience.kmath.nd.mutableStructureND import space.kscience.kmath.nd.mutableStructureND
import space.kscience.kmath.nd.ndAlgebra import space.kscience.kmath.nd.ndAlgebra
import space.kscience.kmath.structures.Float64 import space.kscience.kmath.structures.Float64
import space.kscience.kmath.viktor.viktorAlgebra import space.kscience.kmath.viktor.viktorAlgebra
import kotlin.collections.component1
import kotlin.collections.component2
fun main() { fun main() {
val viktorStructure = Float64Field.viktorAlgebra.mutableStructureND(2, 2) { (i, j) -> val viktorStructure = Float64Field.viktorAlgebra.mutableStructureND(2, 2) { (i, j) ->
if (i == j) 2.0 else 0.0 if (i == j) 2.0 else 0.0
} }
val cmMatrix: Structure2D<Float64> = CMLinearSpace.matrix(2, 2)(0.0, 1.0, 0.0, 3.0) val cmMatrix: Structure2D<Float64> = CMLinearSpace.MatrixBuilder(2, 2).fill(0.0, 1.0, 0.0, 3.0)
val res: Float64BufferND = Float64Field.ndAlgebra { val res: Float64BufferND = Float64Field.ndAlgebra {
exp(viktorStructure) + 2.0 * cmMatrix exp(viktorStructure) + 2.0 * cmMatrix

@ -5,7 +5,7 @@
package space.kscience.kmath.linear package space.kscience.kmath.linear
import space.kscience.attributes.FlagAttribute import space.kscience.attributes.Attributes
import space.kscience.attributes.SafeType import space.kscience.attributes.SafeType
import space.kscience.attributes.WithType import space.kscience.attributes.WithType
import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.UnstableKMathAPI
@ -13,62 +13,51 @@ import space.kscience.kmath.operations.Ring
import space.kscience.kmath.structures.BufferAccessor2D import space.kscience.kmath.structures.BufferAccessor2D
import space.kscience.kmath.structures.MutableBufferFactory import space.kscience.kmath.structures.MutableBufferFactory
public class MatrixBuilder<T : Any, out A : Ring<T>>( /**
* A builder for matrix with fixed size
*/
@UnstableKMathAPI
public class MatrixBuilder<T, out A : Ring<T>>(
public val linearSpace: LinearSpace<T, A>, public val linearSpace: LinearSpace<T, A>,
public val rows: Int, public val rowNum: Int,
public val columns: Int, public val colNum: Int,
) : WithType<T> { ) : WithType<T> {
override val type: SafeType<T> get() = linearSpace.type override val type: SafeType<T> get() = linearSpace.type
public operator fun invoke(vararg elements: T): Matrix<T> { }
require(rows * columns == elements.size) { "The number of elements ${elements.size} is not equal $rows * $columns" }
return linearSpace.buildMatrix(rows, columns) { i, j -> elements[i * columns + j] }
}
//TODO add specific matrix builder functions like diagonal, etc @UnstableKMathAPI
public fun <T, A : Ring<T>> MatrixBuilder<T, A>.sparse(): SparseMatrix<T> =
SparseMatrix(rowNum, colNum, linearSpace.elementAlgebra.zero)
@UnstableKMathAPI
public fun <T, A : Ring<T>> MatrixBuilder<T, A>.fill(vararg elements: T): Matrix<T> {
require(rowNum * colNum == elements.size) { "The number of elements ${elements.size} is not equal $rowNum * $colNum" }
return linearSpace.buildMatrix(rowNum, colNum) { i, j -> elements[i * colNum + j] }
} }
/** /**
* Create a matrix builder with given number of rows and columns * Create a matrix builder with given number of rows and columns
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public fun <T : Any, A : Ring<T>> LinearSpace<T, A>.matrix(rows: Int, columns: Int): MatrixBuilder<T, A> = public fun <T, A : Ring<T>> LinearSpace<T, A>.MatrixBuilder(rows: Int, columns: Int): MatrixBuilder<T, A> =
MatrixBuilder(this, rows, columns) MatrixBuilder(this, rows, columns)
@UnstableKMathAPI
public fun <T : Any> LinearSpace<T, Ring<T>>.vector(vararg elements: T): Point<T> {
return buildVector(elements.size) { elements[it] }
}
public inline fun <T : Any> LinearSpace<T, Ring<T>>.row(
size: Int,
crossinline builder: (Int) -> T,
): Matrix<T> = buildMatrix(1, size) { _, j -> builder(j) }
public fun <T : Any> LinearSpace<T, Ring<T>>.row(vararg values: T): Matrix<T> = row(values.size, values::get)
public inline fun <T : Any> LinearSpace<T, Ring<T>>.column(
size: Int,
crossinline builder: (Int) -> T,
): Matrix<T> = buildMatrix(size, 1) { i, _ -> builder(i) }
public fun <T : Any> LinearSpace<T, Ring<T>>.column(vararg values: T): Matrix<T> = column(values.size, values::get)
public object Symmetric : MatrixAttribute<Unit>, FlagAttribute
/** /**
* Naive implementation of a symmetric matrix builder, that adds a [Symmetric] tag. The resulting matrix contains * Naive implementation of a symmetric matrix builder, that adds a [Symmetric] tag.
* full `size^2` number of elements, but caches elements during calls to save [builder] calls. [builder] is always called in the * The resulting matrix contains full `size^2` number of elements,
* upper triangle region meaning that `i <= j` * but caches elements during calls to save [builder] calls.
* Always called in the upper triangle region meaning that `i <= j`
*/ */
public fun <T : Any, A : Ring<T>> MatrixBuilder<T, A>.symmetric( @UnstableKMathAPI
builder: (i: Int, j: Int) -> T, public fun <T, A : Ring<T>> MatrixBuilder<T, A>.symmetric(
builder: A.(i: Int, j: Int) -> T,
): Matrix<T> { ): Matrix<T> {
require(columns == rows) { "In order to build symmetric matrix, number of rows $rows should be equal to number of columns $columns" } require(colNum == rowNum) { "In order to build symmetric matrix, number of rows $rowNum should be equal to number of columns $colNum" }
return with(BufferAccessor2D<T?>(rows, rows, MutableBufferFactory(type))) { return with(BufferAccessor2D<T?>(rowNum, rowNum, MutableBufferFactory(type))) {
val cache = HashMap<IntArray, T>() val cache = HashMap<IntArray, T>()
linearSpace.buildMatrix(rows, rows) { i, j -> linearSpace.buildMatrix(this@symmetric.rowNum, this@symmetric.rowNum) { i, j ->
val index = intArrayOf(i, j) val index = intArrayOf(i, j)
val cached = cache[index] val cached = cache[index]
if (cached == null) { if (cached == null) {
@ -81,4 +70,50 @@ public fun <T : Any, A : Ring<T>> MatrixBuilder<T, A>.symmetric(
} }
}.withAttribute(Symmetric) }.withAttribute(Symmetric)
} }
} }
/**
* Create a diagonal matrix with given factory.
*/
@UnstableKMathAPI
public fun <T, A : Ring<T>> MatrixBuilder<T, A>.diagonal(
builder: A.(Int) -> T
): Matrix<T> = with(linearSpace.elementAlgebra) {
require(colNum == rowNum) { "In order to build symmetric matrix, number of rows $rowNum should be equal to number of columns $colNum" }
return VirtualMatrix(rowNum, colNum, attributes = Attributes(IsDiagonal)) { i, j ->
check(i in 0 until rowNum) { "$i out of bounds: 0..<$rowNum" }
check(j in 0 until colNum) { "$j out of bounds: 0..<$colNum" }
if (i == j) {
builder(i)
} else {
zero
}
}
}
/**
* Create a diagonal matrix from elements
*/
@UnstableKMathAPI
public fun <T> MatrixBuilder<T, Ring<T>>.diagonal(vararg elements: T): Matrix<T> {
require(colNum == rowNum) { "In order to build symmetric matrix, number of rows $rowNum should be equal to number of columns $colNum" }
return return VirtualMatrix(rowNum, colNum, attributes = Attributes(IsDiagonal)) { i, j ->
check(i in 0 until rowNum) { "$i out of bounds: 0..<$rowNum" }
check(j in 0 until colNum) { "$j out of bounds: 0..<$colNum" }
if (i == j) {
elements[i]
} else {
linearSpace.elementAlgebra.zero
}
}
}
/**
* Create a lazily evaluated virtual matrix with a given size
*/
@UnstableKMathAPI
public fun <T : Any> MatrixBuilder<T, *>.virtual(
attributes: Attributes = Attributes.EMPTY,
generator: (i: Int, j: Int) -> T,
): VirtualMatrix<T> = VirtualMatrix(rowNum, colNum, attributes, generator)

@ -0,0 +1,69 @@
/*
* 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.linear
import space.kscience.attributes.Attributes
import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.operations.Ring
/**
* Mutable sparse matrix that stores values only for non-zero cells ([DOK format](https://en.wikipedia.org/wiki/Sparse_matrix#Dictionary_of_keys_(DOK))).
*
* [SparseMatrix] is ineffective, but does not depend on particular [LinearSpace]
*
* Using this class is almost always a [PerformancePitfall]. It should be used only for special cases like manual matrix building.
*/
@UnstableKMathAPI
public class SparseMatrix<T>(
override val rowNum: Int,
override val colNum: Int,
private val zero: T,
cells: Map<Pair<Int, Int>, T> = emptyMap(),
override val attributes: Attributes = Attributes.EMPTY,
) : MutableMatrix<T> {
private val cells = cells.toMutableMap()
override fun get(i: Int, j: Int): T {
if (i !in 0 until rowNum) throw IndexOutOfBoundsException("Row index $i out of row range 0..$rowNum")
if (j !in 0 until colNum) throw IndexOutOfBoundsException("Column index $j out of column range 0..$colNum")
return cells[i to j] ?: zero
}
override fun set(i: Int, j: Int, value: T) {
require(i in 0 until rowNum) { "Row index $i is out of bounds: 0..<$rowNum" }
require(j in 0 until colNum) { "Row index $j is out of bounds: 0..<$colNum" }
val coordinates = i to j
if (cells[coordinates] != null || value != zero) {
cells[coordinates] = value
}
}
override fun set(index: IntArray, value: T) {
require(index.size == 2) { "Index array must contain two elements." }
set(index[0], index[1], value)
}
}
@UnstableKMathAPI
public fun <T> SparseMatrix<T>.fill(vararg elements: T): SparseMatrix<T> {
require(rowNum * colNum == elements.size) { "The number of elements ${elements.size} is not equal $rowNum * $colNum" }
for (i in 0 until rowNum) {
for (j in 0 until colNum) {
set(i, j, elements[i * rowNum + j])
}
}
return this
}
@UnstableKMathAPI
public fun <T> LinearSpace<T, Ring<T>>.sparse(
rows: Int,
columns: Int,
attributes: Attributes = Attributes.EMPTY,
builder: SparseMatrix<T>.() -> Unit = {}
): SparseMatrix<T> = SparseMatrix(rows, columns, elementAlgebra.zero, attributes = attributes).apply(builder)

@ -6,7 +6,6 @@
package space.kscience.kmath.linear package space.kscience.kmath.linear
import space.kscience.attributes.Attributes import space.kscience.attributes.Attributes
import space.kscience.kmath.nd.ShapeND
/** /**
@ -20,13 +19,5 @@ public class VirtualMatrix<out T>(
override val attributes: Attributes = Attributes.EMPTY, override val attributes: Attributes = Attributes.EMPTY,
public val generator: (i: Int, j: Int) -> T, public val generator: (i: Int, j: Int) -> T,
) : Matrix<T> { ) : Matrix<T> {
override val shape: ShapeND get() = ShapeND(rowNum, colNum)
override operator fun get(i: Int, j: Int): T = generator(i, j) override operator fun get(i: Int, j: Int): T = generator(i, j)
} }
public fun <T : Any> MatrixBuilder<T, *>.virtual(
attributes: Attributes = Attributes.EMPTY,
generator: (i: Int, j: Int) -> T,
): VirtualMatrix<T> = VirtualMatrix(rows, columns, attributes, generator)

@ -23,10 +23,17 @@ public interface MatrixScope<T> : WithType<T>
*/ */
public interface MatrixAttribute<T> : StructureAttribute<T> public interface MatrixAttribute<T> : StructureAttribute<T>
/**
* Matrices with this feature are symmetric, meaning `matrix[i,j] == matrix[j,i]`
*/
public interface Symmetric : MatrixAttribute<Unit>, FlagAttribute{
public companion object: Symmetric
}
/** /**
* Matrices with this feature are considered to have only diagonal non-zero elements. * Matrices with this feature are considered to have only diagonal non-zero elements.
*/ */
public interface IsDiagonal : MatrixAttribute<Unit>, FlagAttribute { public interface IsDiagonal : Symmetric {
public companion object : IsDiagonal public companion object : IsDiagonal
} }

@ -0,0 +1,81 @@
/*
* 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.linear
import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.operations.Ring
/**
* Create a vector from elements
*/
public fun <T> LinearSpace<T, Ring<T>>.vector(vararg elements: T): Point<T> =
buildVector(elements.size) { elements[it] }
/**
* Create a single row matrix
*/
public inline fun <T, A : Ring<T>> LinearSpace<T, A>.row(
size: Int,
crossinline builder: A.(Int) -> T,
): Matrix<T> = buildMatrix(1, size) { _, j -> builder(j) }
/**
* Create a single row matrix from elements
*/
public fun <T> LinearSpace<T, Ring<T>>.row(vararg elements: T): Matrix<T> = row(elements.size) { elements[it] }
/**
* Create a single column matrix
*/
public inline fun <T, A : Ring<T>> LinearSpace<T, A>.column(
size: Int,
crossinline builder: A.(Int) -> T,
): Matrix<T> = buildMatrix(size, 1) { i, _ -> builder(i) }
/**
* Create a single column matrix from elements
*/
public fun <T> LinearSpace<T, Ring<T>>.column(vararg elements: T): Matrix<T> = column(elements.size) { elements[it] }
/**
* Stack vertically several matrices with the same number of columns.
*
* Resulting matrix number of rows is the sum of rows in all [matrices]
*/
@PerformancePitfall
@UnstableKMathAPI
public fun <T> LinearSpace<T, Ring<T>>.vstack(vararg matrices: Matrix<T>): Matrix<T> {
require(matrices.isNotEmpty()) { "No matrices" }
val colNum = matrices.first().colNum
require(matrices.all { it.colNum == colNum }) { "All matrices must have the same number of columns: $colNum" }
val rows = matrices.flatMap { it.rows }
return buildMatrix(matrices.sumOf { it.rowNum }, colNum) { row, column->
rows[row][column]
}
}
/**
* Stack horizontally several matrices with the same number of rows.
*
* Resulting matrix number of co is the sum of rows in all [matrices]
*/
@PerformancePitfall
@UnstableKMathAPI
public fun <T> LinearSpace<T, Ring<T>>.hstack(vararg matrices: Matrix<T>): Matrix<T> {
require(matrices.isNotEmpty()) { "No matrices" }
val rowNum = matrices.first().rowNum
require(matrices.all { it.rowNum == rowNum }) { "All matrices must have the same number of rows: $rowNum" }
val columns = matrices.flatMap { it.columns }
return buildMatrix(rowNum, matrices.sumOf { it.colNum }) { row, column->
columns[column][row]
}
}

@ -228,7 +228,6 @@ public interface MutableStructureND<T> : StructureND<T> {
* @param index the indices. * @param index the indices.
* @param value the value. * @param value the value.
*/ */
@PerformancePitfall
public operator fun set(index: IntArray, value: T) public operator fun set(index: IntArray, value: T)
} }

@ -10,7 +10,7 @@ import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.structures.MutableBufferFactory import space.kscience.kmath.structures.MutableBufferFactory
/** /**
* An algebra for generic boolean logic * Algebra for generic boolean logic
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public interface LogicAlgebra<T : Any> : Algebra<T> { public interface LogicAlgebra<T : Any> : Algebra<T> {

@ -22,7 +22,7 @@ internal class BufferAccessor2D<T>(
} }
inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer<T> = inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer<T> =
factory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } factory(this@BufferAccessor2D.rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) }
fun create(mat: Structure2D<T>): MutableBuffer<T> = create { i, j -> mat[i, j] } fun create(mat: Structure2D<T>): MutableBuffer<T> = create { i, j -> mat[i, j] }

@ -29,7 +29,7 @@ class DoubleLUSolverTest {
@Test @Test
fun testDecomposition() = with(Double.algebra.linearSpace) { fun testDecomposition() = with(Double.algebra.linearSpace) {
val matrix = matrix(2, 2)( val matrix = MatrixBuilder(2, 2).fill(
3.0, 1.0, 3.0, 1.0,
2.0, 3.0 2.0, 3.0
) )
@ -44,14 +44,14 @@ class DoubleLUSolverTest {
@Test @Test
fun testInvert() = Double.algebra.linearSpace.run { fun testInvert() = Double.algebra.linearSpace.run {
val matrix = matrix(2, 2)( val matrix = MatrixBuilder(2, 2).fill(
3.0, 1.0, 3.0, 1.0,
1.0, 3.0 1.0, 3.0
) )
val inverted = lupSolver().inverse(matrix) val inverted = lupSolver().inverse(matrix)
val expected = matrix(2, 2)( val expected = MatrixBuilder(2, 2).fill(
0.375, -0.125, 0.375, -0.125,
-0.125, 0.375 -0.125, 0.375
) )

@ -0,0 +1,38 @@
/*
* 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.linear
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.algebra
import space.kscience.kmath.structures.Float64
import kotlin.test.Test
import kotlin.test.assertEquals
@UnstableKMathAPI
class MatrixBuilderTest {
@Test
fun buildCompositeMatrix() = with(Float64.algebra.linearSpace) {
val matrix = vstack(
sparse(1, 5) { set(0, 4, 1.0) },
hstack(
sparse(4, 4).fill(
1.0, 1.0, 0.0, 0.0,
0.0, 1.0, 1.0, 0.0,
0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 0.0, 1.0
),
sparse(4, 1)
)
)
println(StructureND.toString(matrix))
assertEquals(1.0, matrix[0, 4])
}
}

@ -29,7 +29,7 @@ class MatrixTest {
@Test @Test
fun testBuilder() = Double.algebra.linearSpace.run { fun testBuilder() = Double.algebra.linearSpace.run {
val matrix = matrix(2, 3)( val matrix = MatrixBuilder(2, 3).fill(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 2.0 0.0, 1.0, 2.0
) )

@ -28,7 +28,7 @@ class ParallelMatrixTest {
@Test @Test
fun testBuilder() = Float64Field.linearSpace.parallel { fun testBuilder() = Float64Field.linearSpace.parallel {
val matrix = matrix(2, 3)( val matrix = MatrixBuilder(2, 3).fill(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 2.0 0.0, 1.0, 2.0
) )

@ -37,8 +37,8 @@ public fun realMatrix(rowNum: Int, colNum: Int, initializer: Float64Field.(i: In
Double.algebra.linearSpace.buildMatrix(rowNum, colNum, initializer) Double.algebra.linearSpace.buildMatrix(rowNum, colNum, initializer)
@OptIn(UnstableKMathAPI::class) @OptIn(UnstableKMathAPI::class)
public fun realMatrix(rowNum: Int, colNum: Int): MatrixBuilder<Double, Float64Field> = public fun MatrixBuilder(rowNum: Int, colNum: Int): MatrixBuilder<Double, Float64Field> =
Double.algebra.linearSpace.matrix(rowNum, colNum) Double.algebra.linearSpace.MatrixBuilder(rowNum, colNum)
public fun Array<DoubleArray>.toMatrix(): RealMatrix { public fun Array<DoubleArray>.toMatrix(): RealMatrix {
return Double.algebra.linearSpace.buildMatrix(size, this[0].size) { row, col -> this@toMatrix[row][col] } return Double.algebra.linearSpace.buildMatrix(size, this[0].size) { row, col -> this@toMatrix[row][col] }

@ -7,8 +7,9 @@ package space.kscience.kmath.real
import space.kscience.kmath.PerformancePitfall import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.linear.MatrixBuilder
import space.kscience.kmath.linear.fill
import space.kscience.kmath.linear.linearSpace import space.kscience.kmath.linear.linearSpace
import space.kscience.kmath.linear.matrix
import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.algebra import space.kscience.kmath.operations.algebra
import space.kscience.kmath.testutils.contentEquals import space.kscience.kmath.testutils.contentEquals
@ -43,11 +44,11 @@ internal class DoubleMatrixTest {
@Test @Test
fun testRepeatStackVertical() { fun testRepeatStackVertical() {
val matrix1 = realMatrix(2, 3)( val matrix1 = MatrixBuilder(2, 3).fill(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 2.0 0.0, 1.0, 2.0
) )
val matrix2 = realMatrix(6, 3)( val matrix2 = MatrixBuilder(6, 3).fill(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 2.0, 0.0, 1.0, 2.0,
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
@ -60,12 +61,12 @@ internal class DoubleMatrixTest {
@Test @Test
fun testMatrixAndDouble() = Double.algebra.linearSpace.run { fun testMatrixAndDouble() = Double.algebra.linearSpace.run {
val matrix1 = realMatrix(2, 3)( val matrix1 = space.kscience.kmath.real.MatrixBuilder(2, 3).fill(
1.0, 0.0, 3.0, 1.0, 0.0, 3.0,
4.0, 6.0, 2.0 4.0, 6.0, 2.0
) )
val matrix2 = (matrix1 * 2.5 + 1.0 - 2.0) / 2.0 val matrix2 = (matrix1 * 2.5 + 1.0 - 2.0) / 2.0
val expectedResult = matrix(2, 3)( val expectedResult = this.MatrixBuilder(2, 3).fill(
0.75, -0.5, 3.25, 0.75, -0.5, 3.25,
4.5, 7.0, 2.0 4.5, 7.0, 2.0
) )
@ -74,13 +75,13 @@ internal class DoubleMatrixTest {
@Test @Test
fun testDoubleAndMatrix() { fun testDoubleAndMatrix() {
val matrix1 = realMatrix(2, 3)( val matrix1 = MatrixBuilder(2, 3).fill(
1.0, 0.0, 3.0, 1.0, 0.0, 3.0,
4.0, 6.0, 2.0 4.0, 6.0, 2.0
) )
val matrix2 = 20.0 - (10.0 + (5.0 * matrix1)) val matrix2 = 20.0 - (10.0 + (5.0 * matrix1))
//val matrix2 = 10.0 + (5.0 * matrix1) //val matrix2 = 10.0 + (5.0 * matrix1)
val expectedResult = realMatrix(2, 3)( val expectedResult = MatrixBuilder(2, 3).fill(
5.0, 10.0, -5.0, 5.0, 10.0, -5.0,
-10.0, -20.0, 0.0 -10.0, -20.0, 0.0
) )
@ -89,15 +90,15 @@ internal class DoubleMatrixTest {
@Test @Test
fun testSquareAndPower() { fun testSquareAndPower() {
val matrix1 = realMatrix(2, 3)( val matrix1 = MatrixBuilder(2, 3).fill(
-1.0, 0.0, 3.0, -1.0, 0.0, 3.0,
4.0, -6.0, -2.0 4.0, -6.0, -2.0
) )
val matrix2 = realMatrix(2, 3)( val matrix2 = MatrixBuilder(2, 3).fill(
1.0, 0.0, 9.0, 1.0, 0.0, 9.0,
16.0, 36.0, 4.0 16.0, 36.0, 4.0
) )
val matrix3 = realMatrix(2, 3)( val matrix3 = MatrixBuilder(2, 3).fill(
-1.0, 0.0, 27.0, -1.0, 0.0, 27.0,
64.0, -216.0, -8.0 64.0, -216.0, -8.0
) )
@ -108,16 +109,16 @@ internal class DoubleMatrixTest {
@OptIn(UnstableKMathAPI::class) @OptIn(UnstableKMathAPI::class)
@Test @Test
fun testTwoMatrixOperations() { fun testTwoMatrixOperations() {
val matrix1 = realMatrix(2, 3)( val matrix1 = MatrixBuilder(2, 3).fill(
-1.0, 0.0, 3.0, -1.0, 0.0, 3.0,
4.0, -6.0, 7.0 4.0, -6.0, 7.0
) )
val matrix2 = realMatrix(2, 3)( val matrix2 = MatrixBuilder(2, 3).fill(
1.0, 0.0, 3.0, 1.0, 0.0, 3.0,
4.0, 6.0, -2.0 4.0, 6.0, -2.0
) )
val result = matrix1 * matrix2 + matrix1 - matrix2 val result = matrix1 * matrix2 + matrix1 - matrix2
val expectedResult = realMatrix(2, 3)( val expectedResult = MatrixBuilder(2, 3).fill(
-3.0, 0.0, 9.0, -3.0, 0.0, 9.0,
16.0, -48.0, -5.0 16.0, -48.0, -5.0
) )
@ -126,16 +127,16 @@ internal class DoubleMatrixTest {
@Test @Test
fun testColumnOperations() { fun testColumnOperations() {
val matrix1 = realMatrix(2, 4)( val matrix1 = MatrixBuilder(2, 4).fill(
-1.0, 0.0, 3.0, 15.0, -1.0, 0.0, 3.0, 15.0,
4.0, -6.0, 7.0, -11.0 4.0, -6.0, 7.0, -11.0
) )
val matrix2 = realMatrix(2, 5)( val matrix2 = MatrixBuilder(2, 5).fill(
-1.0, 0.0, 3.0, 15.0, -1.0, -1.0, 0.0, 3.0, 15.0, -1.0,
4.0, -6.0, 7.0, -11.0, 4.0 4.0, -6.0, 7.0, -11.0, 4.0
) )
val col1 = realMatrix(2, 1)(0.0, -6.0) val col1 = MatrixBuilder(2, 1).fill(0.0, -6.0)
val cols1to2 = realMatrix(2, 2)( val cols1to2 = MatrixBuilder(2, 2).fill(
0.0, 3.0, 0.0, 3.0,
-6.0, 7.0 -6.0, 7.0
) )
@ -160,7 +161,7 @@ internal class DoubleMatrixTest {
@Test @Test
fun testAllElementOperations() = Double.algebra.linearSpace.run { fun testAllElementOperations() = Double.algebra.linearSpace.run {
val matrix1 = matrix(2, 4)( val matrix1 = this.MatrixBuilder(2, 4).fill(
-1.0, 0.0, 3.0, 15.0, -1.0, 0.0, 3.0, 15.0,
4.0, -6.0, 7.0, -11.0 4.0, -6.0, 7.0, -11.0
) )

@ -8,10 +8,7 @@ package space.kscience.kmath.geometry.euclidean3d
import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.complex.* import space.kscience.kmath.complex.*
import space.kscience.kmath.geometry.* import space.kscience.kmath.geometry.*
import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.*
import space.kscience.kmath.linear.Matrix
import space.kscience.kmath.linear.linearSpace
import space.kscience.kmath.linear.matrix
import space.kscience.kmath.operations.Float64Field import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.structures.Float64 import space.kscience.kmath.structures.Float64
import kotlin.math.* import kotlin.math.*
@ -103,7 +100,7 @@ public fun Quaternion.toRotationMatrix(
linearSpace: LinearSpace<Double, *> = Float64Field.linearSpace, linearSpace: LinearSpace<Double, *> = Float64Field.linearSpace,
): Matrix<Float64> { ): Matrix<Float64> {
val s = QuaternionAlgebra.norm(this).pow(-2) val s = QuaternionAlgebra.norm(this).pow(-2)
return linearSpace.matrix(3, 3)( return linearSpace.MatrixBuilder(3, 3).fill(
1.0 - 2 * s * (y * y + z * z), 2 * s * (x * y - z * w), 2 * s * (x * z + y * w), 1.0 - 2 * s * (y * y + z * z), 2 * s * (x * y - z * w), 2 * s * (x * z + y * w),
2 * s * (x * y + z * w), 1.0 - 2 * s * (x * x + z * z), 2 * s * (y * z - x * w), 2 * s * (x * y + z * w), 1.0 - 2 * s * (x * x + z * z), 2 * s * (y * z - x * w),
2 * s * (x * z - y * w), 2 * s * (y * z + x * w), 1.0 - 2 * s * (x * x + y * y) 2 * s * (x * z - y * w), 2 * s * (y * z + x * w), 1.0 - 2 * s * (x * x + y * y)

@ -15,6 +15,10 @@ kscience {
// api(projects.kmathFunctions) // api(projects.kmathFunctions)
api(libs.ojalgo) api(libs.ojalgo)
} }
jvmTest{
implementation(projects.testUtils)
}
} }
readme { readme {

@ -30,7 +30,7 @@ class OjalgoMatrixTest {
@Test @Test
fun testBuilder() = Ojalgo.Companion.R064.linearSpace { fun testBuilder() = Ojalgo.Companion.R064.linearSpace {
val matrix = buildMatrix(2, 3)( val matrix = MatrixBuilder(2, 3).fill(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 2.0 0.0, 1.0, 2.0
) )
@ -77,7 +77,7 @@ class OjalgoMatrixTest {
@Test @Test
fun testCholesky() = with(Ojalgo.Companion.R064.linearSpace) { fun testCholesky() = with(Ojalgo.Companion.R064.linearSpace) {
val l = buildMatrix(4, 4)( val l = MatrixBuilder(4, 4).fill(
1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0,
1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0,

@ -91,7 +91,7 @@ public object QowOptimizer : Optimizer<Double, XYFit> {
* D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2 * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2
*/ */
private fun QoWeight.covarF(): Matrix<Float64> = private fun QoWeight.covarF(): Matrix<Float64> =
linearSpace.matrix(size, size).symmetric { s1, s2 -> linearSpace.MatrixBuilder(size, size).symmetric { s1, s2 ->
(0 until data.size).sumOf { d -> derivs[d, s1] * derivs[d, s2] / dispersion[d] } (0 until data.size).sumOf { d -> derivs[d, s1] * derivs[d, s2] / dispersion[d] }
} }