Matrix and linear algebra redone

This commit is contained in:
Alexander Nozik 2018-12-24 15:19:35 +03:00
parent deed7f597c
commit 88c20843a4
17 changed files with 418 additions and 356 deletions

View File

@ -1,13 +1,14 @@
buildscript {
extra["kotlinVersion"] = "1.3.11"
extra["kotlinVersion"] = "1.3.20-eap-52"
extra["ioVersion"] = "0.1.2-dev-2"
extra["coroutinesVersion"] = "1.0.1"
extra["coroutinesVersion"] = "1.1.0"
val kotlinVersion: String by extra
val ioVersion: String by extra
val coroutinesVersion: String by extra
repositories {
maven ("https://dl.bintray.com/kotlin/kotlin-eap")
jcenter()
}
@ -28,6 +29,11 @@ allprojects {
group = "scientifik"
version = "0.0.2-dev-1"
repositories{
maven ("https://dl.bintray.com/kotlin/kotlin-eap")
jcenter()
}
}
if(file("artifactory.gradle").exists()){

View File

@ -20,7 +20,7 @@ class FastHistogram(
private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 })
private val values: NDStructure<LongCounter> = ndStructure(strides) { LongCounter() }
private val values: NDStructure<LongCounter> = inlineNDStructure(strides) { LongCounter() }
//private val weight: NDStructure<DoubleCounter?> = ndStructure(strides){null}
@ -93,7 +93,7 @@ class FastHistogram(
* Convert this histogram into NDStructure containing bin values but not bin descriptions
*/
fun asNDStructure(): NDStructure<Number> {
return ndStructure(this.values.shape) { values[it].sum() }
return inlineNdStructure(this.values.shape) { values[it].sum() }
}
/**

View File

@ -2,10 +2,7 @@ package scientifik.kmath.linear
import scientifik.kmath.operations.DoubleField
import scientifik.kmath.operations.Field
import scientifik.kmath.structures.MutableNDStructure
import scientifik.kmath.structures.NDStructure
import scientifik.kmath.structures.get
import scientifik.kmath.structures.mutableNdStructure
import scientifik.kmath.structures.*
import kotlin.math.absoluteValue
/**
@ -13,7 +10,7 @@ import kotlin.math.absoluteValue
*/
abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matrix<T, F>) {
private val field get() = matrix.context.field
private val field get() = matrix.context.ring
/** Entries of LU decomposition. */
internal val lu: NDStructure<T>
/** Pivot permutation associated with LU decomposition. */
@ -34,11 +31,11 @@ abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matr
* @return the L matrix (or null if decomposed matrix is singular)
*/
val l: Matrix<out T, F> by lazy {
matrix.context.produce { i, j ->
matrix.context.produce(matrix.numRows, matrix.numCols) { i, j ->
when {
j < i -> lu[i, j]
j == i -> matrix.context.field.one
else -> matrix.context.field.zero
j == i -> matrix.context.ring.one
else -> matrix.context.ring.zero
}
}
}
@ -51,7 +48,7 @@ abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matr
* @return the U matrix (or null if decomposed matrix is singular)
*/
val u: Matrix<out T, F> by lazy {
matrix.context.produce { i, j ->
matrix.context.produce(matrix.numRows, matrix.numCols) { i, j ->
if (j >= i) lu[i, j] else field.zero
}
}
@ -67,7 +64,7 @@ abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matr
* @see .getPivot
*/
val p: Matrix<out T, F> by lazy {
matrix.context.produce { i, j ->
matrix.context.produce(matrix.numRows, matrix.numCols) { i, j ->
//TODO ineffective. Need sparse matrix for that
if (j == pivot[i]) field.one else field.zero
}
@ -79,9 +76,9 @@ abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matr
*/
val determinant: T
get() {
with(matrix.context.field) {
with(matrix.context.ring) {
var determinant = if (even) one else -one
for (i in 0 until matrix.rows) {
for (i in 0 until matrix.numRows) {
determinant *= lu[i, i]
}
return determinant
@ -97,20 +94,20 @@ abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matr
abstract fun isSingular(value: T): Boolean
private fun abs(value: T) = if (value > matrix.context.field.zero) value else with(matrix.context.field) { -value }
private fun abs(value: T) = if (value > matrix.context.ring.zero) value else with(matrix.context.ring) { -value }
private fun calculateLU(): Pair<NDStructure<T>, IntArray> {
if (matrix.rows != matrix.columns) {
if (matrix.numRows != matrix.numCols) {
error("LU decomposition supports only square matrices")
}
val m = matrix.columns
val pivot = IntArray(matrix.rows)
val m = matrix.numCols
val pivot = IntArray(matrix.numRows)
//TODO fix performance
val lu: MutableNDStructure<T> = mutableNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] }
val lu: MutableNDStructure<T> = MutableNdStructure(intArrayOf(matrix.numRows, matrix.numCols), ::boxingMutableBuffer) { index: IntArray -> matrix[index[0], index[1]] }
with(matrix.context.field) {
with(matrix.context.ring) {
// Initialize permutation array and parity
for (row in 0 until m) {
pivot[row] = row
@ -201,50 +198,50 @@ object RealLUSolver : LinearSolver<Double, DoubleField> {
fun decompose(mat: Matrix<Double, DoubleField>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold)
override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix {
val decomposition = decompose(a, a.context.field.zero)
val decomposition = decompose(a, a.context.ring.zero)
if (b.rows != a.rows) {
error("Matrix dimension mismatch expected ${a.rows}, but got ${b.rows}")
if (b.numRows != a.numCols) {
error("Matrix dimension mismatch expected ${a.numRows}, but got ${b.numCols}")
}
// Apply permutations to b
val bp = Array(a.rows) { DoubleArray(b.columns) }
for (row in 0 until a.rows) {
val bp = Array(a.numRows) { DoubleArray(b.numCols) }
for (row in 0 until a.numRows) {
val bpRow = bp[row]
val pRow = decomposition.pivot[row]
for (col in 0 until b.columns) {
for (col in 0 until b.numCols) {
bpRow[col] = b[pRow, col]
}
}
// Solve LY = b
for (col in 0 until a.rows) {
for (col in 0 until a.numRows) {
val bpCol = bp[col]
for (i in col + 1 until a.rows) {
for (i in col + 1 until a.numRows) {
val bpI = bp[i]
val luICol = decomposition.lu[i, col]
for (j in 0 until b.columns) {
for (j in 0 until b.numCols) {
bpI[j] -= bpCol[j] * luICol
}
}
}
// Solve UX = Y
for (col in a.rows - 1 downTo 0) {
for (col in a.numRows - 1 downTo 0) {
val bpCol = bp[col]
val luDiag = decomposition.lu[col, col]
for (j in 0 until b.columns) {
for (j in 0 until b.numCols) {
bpCol[j] /= luDiag
}
for (i in 0 until col) {
val bpI = bp[i]
val luICol = decomposition.lu[i, col]
for (j in 0 until b.columns) {
for (j in 0 until b.numCols) {
bpI[j] -= bpCol[j] * luICol
}
}
}
return a.context.produce { i, j -> bp[i][j] }
return a.context.produce(a.numRows, a.numCols) { i, j -> bp[i][j] }
}
}

View File

@ -3,6 +3,9 @@ package scientifik.kmath.linear
import scientifik.kmath.operations.DoubleField
import scientifik.kmath.operations.Field
import scientifik.kmath.operations.Norm
import scientifik.kmath.operations.Ring
import scientifik.kmath.structures.asSequence
import scientifik.kmath.structures.boxingBuffer
/**
@ -11,23 +14,22 @@ import scientifik.kmath.operations.Norm
interface LinearSolver<T : Any, F : Field<T>> {
fun solve(a: Matrix<T, F>, b: Matrix<T, F>): Matrix<T, F>
fun solve(a: Matrix<T, F>, b: Vector<T, F>): Vector<T, F> = solve(a, b.toMatrix()).toVector()
fun inverse(a: Matrix<T, F>): Matrix<T, F> = solve(a, Matrix.diagonal(a.rows, a.columns, a.context.field))
fun inverse(a: Matrix<T, F>): Matrix<T, F> = solve(a, a.context.one)
}
/**
* Convert vector to array (copying content of array)
*/
fun <T : Any> Array<T>.toVector(field: Field<T>) = Vector.of(size, field) { this[it] }
fun <T : Any> Array<T>.toVector(field: Field<T>) = Vector.generic(size, field) { this[it] }
fun DoubleArray.toVector() = Vector.ofReal(this.size) { this[it] }
fun List<Double>.toVector() = Vector.ofReal(this.size) { this[it] }
fun DoubleArray.toVector() = Vector.real(this.size) { this[it] }
fun List<Double>.toVector() = Vector.real(this.size) { this[it] }
/**
* Convert matrix to vector if it is possible
*/
fun <T : Any, F : Field<T>> Matrix<T, F>.toVector(): Vector<T, F> {
return when {
this.columns == 1 -> {
fun <T : Any, F : Ring<T>> Matrix<T, F>.toVector(): Vector<T, F> {
return if (this.numCols == 1) {
// if (this is ArrayMatrix) {
// //Reuse existing underlying array
// ArrayVector(ArrayVectorSpace(rows, context.field, context.ndFactory), array)
@ -35,26 +37,27 @@ fun <T : Any, F : Field<T>> Matrix<T, F>.toVector(): Vector<T, F> {
// //Generic vector
// vector(rows, context.field) { get(it, 0) }
// }
Vector.of(rows, context.field) { get(it, 0) }
}
else -> error("Can't convert matrix with more than one column to vector")
}
Vector.generic(numRows, context.ring) { get(it, 0) }
} else error("Can't convert matrix with more than one column to vector")
}
fun <T : Any, F : Field<T>> Vector<T, F>.toMatrix(): Matrix<T, F> {
fun <T : Any, R : Ring<T>> Vector<T, R>.toMatrix(): Matrix<T, R> {
// val context = StructureMatrixContext(size, 1, context.space)
//
// return if (this is ArrayVector) {
// //Reuse existing underlying array
// ArrayMatrix(ArrayMatrixSpace(size, 1, context.field, context.ndFactory), array)
// StructureMatrix(context,this.buffer)
// } else {
// //Generic vector
// matrix(size, 1, context.field) { i, j -> get(i) }
// }
return Matrix.of(size, 1, context.space) { i, _ -> get(i) }
//return Matrix.of(size, 1, context.space) { i, _ -> get(i) }
return StructureMatrixSpace(size, 1, context.space, ::boxingBuffer).produce { i, _ -> get(i) }
}
object VectorL2Norm : Norm<Vector<out Number, *>, Double> {
override fun norm(arg: Vector<out Number, *>): Double {
return kotlin.math.sqrt(arg.sumByDouble { it.toDouble() })
return kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() })
}
}

View File

@ -1,198 +1,156 @@
package scientifik.kmath.linear
import scientifik.kmath.histogram.Point
import scientifik.kmath.operations.*
import scientifik.kmath.operations.DoubleField
import scientifik.kmath.operations.Ring
import scientifik.kmath.operations.Space
import scientifik.kmath.operations.SpaceElement
import scientifik.kmath.structures.*
interface MatrixSpace<T : Any, R : Ring<T>> : Space<Matrix<T, R>> {
/**
* The space for linear elements. Supports scalar product alongside with standard linear operations.
* @param T type of individual element of the vector or matrix
* @param V the type of vector space element
* The ring context for matrix elements
*/
abstract class MatrixSpace<T : Any, F : Ring<T>>(val rows: Int, val columns: Int, val field: F) : Space<Matrix<T, F>> {
val ring: R
val rowNum: Int
val colNum: Int
val shape get() = intArrayOf(rowNum, colNum)
/**
* Produce the element of this space
* Produce a matrix with this context and given dimensions
*/
abstract fun produce(initializer: (Int, Int) -> T): Matrix<T, F>
fun produce(rows: Int = rowNum, columns: Int = colNum, initializer: (i: Int, j: Int) -> T): Matrix<T, R>
/**
* Produce new matrix space with given dimensions. The space produced could be raised from cache since [MatrixSpace] does not have mutable elements
* Produce a point compatible with matrix space
*/
abstract fun produceSpace(rows: Int, columns: Int): MatrixSpace<T, F>
fun point(size: Int, initializer: (Int) -> T): Point<T>
override val zero: Matrix<T, F> by lazy {
produce { _, _ -> field.zero }
}
override val zero: Matrix<T, R> get() = produce { _, _ -> ring.zero }
// val one: Matrix<T> by lazy {
// produce { i, j -> if (i == j) field.one else field.zero }
// }
val one get() = produce { i, j -> if (i == j) ring.one else ring.zero }
override fun add(a: Matrix<T, F>, b: Matrix<T, F>): Matrix<T, F> {
return produce { i, j -> with(field) { a[i, j] + b[i, j] } }
}
override fun add(a: Matrix<T, R>, b: Matrix<T, R>): Matrix<T, R> = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } }
override fun multiply(a: Matrix<T, F>, k: Double): Matrix<T, F> {
//TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser
return produce { i, j -> with(field) { a[i, j] * k } }
}
override fun multiply(a: Matrix<T, R>, k: Double): Matrix<T, R> = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is MatrixSpace<*,*>) return false
companion object {
/**
* Non-boxing double matrix
*/
fun real(rows: Int, columns: Int): MatrixSpace<Double, DoubleField> = StructureMatrixSpace(rows, columns, DoubleField, DoubleBufferFactory)
if (rows != other.rows) return false
if (columns != other.columns) return false
if (field != other.field) return false
/**
* A structured matrix with custom buffer
*/
fun <T : Any, R : Ring<T>> buffered(rows: Int, columns: Int, ring: R, bufferFactory: BufferFactory<T> = ::boxingBuffer): MatrixSpace<T, R> = StructureMatrixSpace(rows, columns, ring, bufferFactory)
return true
}
override fun hashCode(): Int {
var result = rows
result = 31 * result + columns
result = 31 * result + field.hashCode()
return result
/**
* Automatic buffered matrix, unboxed if it is possible
*/
inline fun <reified T : Any, R : Ring<T>> smart(rows: Int, columns: Int, ring: R): MatrixSpace<T, R> = buffered(rows, columns, ring, ::inlineBuffer)
}
}
/**
* A matrix-like structure
*/
interface Matrix<T : Any, F: Ring<T>> : SpaceElement<Matrix<T, F>, MatrixSpace<T, F>> {
/**
* Number of rows
*/
val rows: Int
/**
* Number of columns
*/
val columns: Int
/**
* Get element in row [i] and column [j]. Throws error in case of call ounside structure dimensions
* Specialized 2-d structure
*/
interface Matrix<T : Any, R : Ring<T>> : NDStructure<T>, SpaceElement<Matrix<T, R>, MatrixSpace<T, R>> {
operator fun get(i: Int, j: Int): T
override val self: Matrix<T, F>
get() = this
override fun get(index: IntArray): T = get(index[0], index[1])
fun transpose(): Matrix<T, F> {
return object : Matrix<T, F> {
override val context: MatrixSpace<T, F> = this@Matrix.context
override val rows: Int = this@Matrix.columns
override val columns: Int = this@Matrix.rows
override fun get(i: Int, j: Int): T = this@Matrix[j, i]
override val shape: IntArray get() = context.shape
val numRows get() = context.rowNum
val numCols get() = context.colNum
//TODO replace by lazy buffers
val rows: List<Point<T>>
get() = (0 until numRows).map { i ->
context.point(numCols) { j -> get(i, j) }
}
val columns: List<Point<T>>
get() = (0 until numCols).map { j ->
context.point(numRows) { i -> get(i, j) }
}
companion object {
/**
* Create [ArrayMatrix] with custom field
*/
fun <T : Any, F: Field<T>> of(rows: Int, columns: Int, field: F, initializer: (Int, Int) -> T) =
ArrayMatrix(ArrayMatrixSpace(rows, columns, field), initializer)
/**
* Create [ArrayMatrix] of doubles. The implementation in general should be faster than generic one due to boxing.
*/
fun ofReal(rows: Int, columns: Int, initializer: (Int, Int) -> Double) =
ArrayMatrix(ArrayMatrixSpace(rows, columns, DoubleField, realNDFieldFactory), initializer)
/**
* Create a diagonal value matrix. By default value equals [Field.one].
*/
fun <T : Any, F: Field<T>> diagonal(rows: Int, columns: Int, field: F, values: (Int) -> T = { field.one }): Matrix<T, F> {
return of(rows, columns, field) { i, j -> if (i == j) values(i) else field.zero }
}
/**
* Equality check on two generic matrices
*/
fun equals(mat1: Matrix<*, *>, mat2: Matrix<*, *>): Boolean {
if (mat1 === mat2) return true
if (mat1.context != mat2.context) return false
for (i in 0 until mat1.rows) {
for (j in 0 until mat2.columns) {
if (mat1[i, j] != mat2[i, j]) return false
fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) =
MatrixSpace.real(rows, columns).produce(rows, columns, initializer)
}
}
return true
infix fun <T : Any, R : Ring<T>> Matrix<T, R>.dot(other: Matrix<T, R>): Matrix<T, R> {
//TODO add typed error
if (this.numCols != other.numRows) error("Matrix dot operation dimension mismatch: ($numRows, $numCols) x (${other.numRows}, ${other.numCols})")
return context.produce(numRows, other.numCols) { i, j ->
val row = rows[i]
val column = other.columns[j]
with(context.ring) {
row.asSequence().zip(column.asSequence(), ::multiply).sum()
}
}
}
/**
* Dot product. Throws exception on dimension mismatch
*/
infix fun <T : Any, F : Field<T>> Matrix<T, F>.dot(b: Matrix<T, F>): Matrix<T, F> {
if (columns != b.rows) {
//TODO replace by specific exception
error("Dimension mismatch in linear structure dot product: [$rows,$columns]*[${b.rows},${b.columns}]")
infix fun <T : Any, R : Ring<T>> Matrix<T, R>.dot(vector: Point<T>): Point<T> {
//TODO add typed error
if (this.numCols != vector.size) error("Matrix dot vector operation dimension mismatch: ($numRows, $numCols) x (${vector.size})")
return context.point(numRows) { i ->
val row = rows[i]
with(context.ring) {
row.asSequence().zip(vector.asSequence(), ::multiply).sum()
}
return context.produceSpace(rows, b.columns).produce { i, j ->
(0 until columns).asSequence().map { k -> context.field.multiply(this[i, k], b[k, j]) }.reduce { first, second -> context.field.add(first, second) }
}
}
/**
* Matrix x Vector dot product.
*/
infix fun <T : Any, F : Field<T>> Matrix<T, F>.dot(b: Point<T>): Matrix<T, F> {
if (columns != b.size) {
//TODO replace by specific exception
error("Dimension mismatch in linear structure dot product: [$rows,$columns]*[${b.size},1]")
}
return context.produceSpace(rows, 1).produce { i, j ->
(0 until columns).asSequence().map { k -> context.field.multiply(this[i, k], b[k]) }.reduce { first, second -> context.field.add(first, second) }
data class StructureMatrixSpace<T : Any, R : Ring<T>>(
override val rowNum: Int,
override val colNum: Int,
override val ring: R,
private val bufferFactory: BufferFactory<T>
) : MatrixSpace<T, R> {
override val shape: IntArray = intArrayOf(rowNum, colNum)
private val strides = DefaultStrides(shape)
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T, R> {
return if (rows == rowNum && columns == colNum) {
val structure = NdStructure(strides, bufferFactory) { initializer(it[0], it[1]) }
StructureMatrix(this, structure)
} else {
val context = StructureMatrixSpace(rows, columns, ring, bufferFactory)
val structure = NdStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) }
StructureMatrix(context, structure)
}
}
typealias NDFieldFactory<T, F> = (IntArray) -> NDField<T, F>
internal fun <T : Any, F : Field<T>> genericNDFieldFactory(field: F): NDFieldFactory<T, F> = { index -> GenericNDField(index, field) }
internal val realNDFieldFactory: NDFieldFactory<Double, DoubleField> = { index -> ExtendedNDField(index, DoubleField) }
/**
* NDArray-based implementation of vector space. By default uses slow [GenericNDField], but could be overridden with custom [NDField] factory.
*/
class ArrayMatrixSpace<T : Any, F : Field<T>>(
rows: Int,
columns: Int,
field: F,
val ndFactory: NDFieldFactory<T, F> = genericNDFieldFactory(field)
) : MatrixSpace<T, F>(rows, columns, field) {
val ndField by lazy {
ndFactory(intArrayOf(rows, columns))
override fun point(size: Int, initializer: (Int) -> T): Point<T> = bufferFactory(size, initializer)
}
override fun produce(initializer: (Int, Int) -> T): Matrix<T, F> = ArrayMatrix(this, initializer)
override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace<T, F> {
return ArrayMatrixSpace(rows, columns, field, ndFactory)
data class StructureMatrix<T : Any, R : Ring<T>>(override val context: StructureMatrixSpace<T, R>, val structure: NDStructure<T>) : Matrix<T, R> {
init {
if (structure.shape.size != 2 || structure.shape[0] != context.rowNum || structure.shape[1] != context.colNum) {
error("Dimension mismatch for structure, (${context.rowNum}, ${context.colNum}) expected, but ${structure.shape} found")
}
}
/**
* Member of [ArrayMatrixSpace] which wraps 2-D array
*/
class ArrayMatrix<T : Any, F : Field<T>> internal constructor(override val context: ArrayMatrixSpace<T, F>, val element: NDElement<T, F>) : Matrix<T, F> {
override val shape: IntArray get() = structure.shape
override val self: Matrix<T, R> get() = this
constructor(context: ArrayMatrixSpace<T, F>, initializer: (Int, Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0], list[1]) })
override fun get(index: IntArray): T = structure[index]
override val rows: Int get() = context.rows
override fun get(i: Int, j: Int): T = structure[i, j]
override val columns: Int get() = context.columns
override fun get(i: Int, j: Int): T {
return element[i, j]
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()
}
override val self: ArrayMatrix<T, F> get() = this
}
//TODO produce transposed matrix via reference without creating new space and structure
fun <T : Any, R : Ring<T>> Matrix<T, R>.transpose(): Matrix<T, R> =
context.produce(numCols, numRows) { i, j -> get(j, i) }

View File

@ -2,32 +2,58 @@ package scientifik.kmath.linear
import scientifik.kmath.histogram.Point
import scientifik.kmath.operations.DoubleField
import scientifik.kmath.operations.Field
import scientifik.kmath.operations.Space
import scientifik.kmath.operations.SpaceElement
import scientifik.kmath.structures.NDElement
import scientifik.kmath.structures.get
import scientifik.kmath.structures.*
/**
* A linear space for vectors.
* Could be used on any point-like structure
*/
abstract class VectorSpace<T : Any, S : Space<T>>(val size: Int, val space: S) : Space<Point<T>> {
interface VectorSpace<T : Any, S : Space<T>> : Space<Point<T>> {
abstract fun produce(initializer: (Int) -> T): Vector<T, S>
val size: Int
override val zero: Vector<T, S> by lazy { produce { space.zero } }
val space: S
fun produce(initializer: (Int) -> T): Vector<T, S>
override val zero: Vector<T, S> get() = produce { space.zero }
override fun add(a: Point<T>, b: Point<T>): Vector<T, S> = produce { with(space) { a[it] + b[it] } }
override fun multiply(a: Point<T>, k: Double): Vector<T, S> = produce { with(space) { a[it] * k } }
//TODO add basis
companion object {
private val realSpaceCache = HashMap<Int, BufferVectorSpace<Double, DoubleField>>()
/**
* Non-boxing double vector space
*/
fun real(size: Int): BufferVectorSpace<Double, DoubleField> {
return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, DoubleField, DoubleBufferFactory) }
}
/**
* A structured vector space with custom buffer
*/
fun <T : Any, S : Space<T>> buffered(size: Int, space: S, bufferFactory: BufferFactory<T> = ::boxingBuffer): VectorSpace<T, S> = BufferVectorSpace(size, space, bufferFactory)
/**
* Automatic buffered vector, unboxed if it is possible
*/
inline fun <reified T : Any, S : Space<T>> smart(size: Int, space: S): VectorSpace<T, S> = buffered(size, space, ::inlineBuffer)
}
}
/**
* A point coupled to the linear space
*/
interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, VectorSpace<T, S>>, Point<T>, Iterable<T> {
interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, VectorSpace<T, S>>, Point<T> {
override val size: Int get() = context.size
override operator fun plus(b: Point<T>): Vector<T, S> = context.add(self, b)
@ -39,65 +65,40 @@ interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, VectorSpace<T,
/**
* Create vector with custom field
*/
fun <T : Any, F : Field<T>> of(size: Int, field: F, initializer: (Int) -> T) =
ArrayVector(ArrayVectorSpace(size, field), initializer)
fun <T : Any, F : Space<T>> generic(size: Int, field: F, initializer: (Int) -> T) =
VectorSpace.buffered(size, field).produce(initializer)
private val realSpaceCache = HashMap<Int, ArrayVectorSpace<Double, DoubleField>>()
fun real(size: Int, initializer: (Int) -> Double) = VectorSpace.real(size).produce(initializer)
fun ofReal(vararg elements: Double) = VectorSpace.real(elements.size).produce{elements[it]}
private fun getRealSpace(size: Int): ArrayVectorSpace<Double, DoubleField> {
return realSpaceCache.getOrPut(size){ArrayVectorSpace(size, DoubleField, realNDFieldFactory)}
}
/**
* Create vector of [Double]
*/
fun ofReal(size: Int, initializer: (Int) -> Double) =
ArrayVector(getRealSpace(size), initializer)
fun ofReal(vararg point: Double) = point.toVector()
fun equals(v1: Vector<*, *>, v2: Vector<*, *>): Boolean {
if (v1 === v2) return true
if (v1.context != v2.context) return false
for (i in 0 until v2.size) {
if (v1[i] != v2[i]) return false
}
return true
}
}
}
class ArrayVectorSpace<T : Any, F : Field<T>>(
size: Int,
field: F,
val ndFactory: NDFieldFactory<T, F> = genericNDFieldFactory(field)
) : VectorSpace<T, F>(size, field) {
val ndField by lazy {
ndFactory(intArrayOf(size))
}
override fun produce(initializer: (Int) -> T): Vector<T, F> = ArrayVector(this, initializer)
data class BufferVectorSpace<T : Any, S : Space<T>>(
override val size: Int,
override val space: S,
val bufferFactory: BufferFactory<T>
) : VectorSpace<T, S> {
override fun produce(initializer: (Int) -> T): Vector<T, S> = BufferVector(this, bufferFactory(size, initializer))
}
class ArrayVector<T : Any, F : Field<T>> internal constructor(override val context: VectorSpace<T, F>, val element: NDElement<T, F>) : Vector<T, F> {
constructor(context: ArrayVectorSpace<T, F>, initializer: (Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0]) })
data class BufferVector<T : Any, S : Space<T>>(override val context: VectorSpace<T, S>, val buffer: Buffer<T>) : Vector<T, S> {
init {
if (context.size != element.shape[0]) {
if (context.size != buffer.size) {
error("Array dimension mismatch")
}
}
override fun get(index: Int): T {
return element[index]
return buffer[index]
}
override val self: ArrayVector<T, F> get() = this
override val self: BufferVector<T, S> get() = this
override fun iterator(): Iterator<T> = (0 until size).map { element[it] }.iterator()
override fun iterator(): Iterator<T> = (0 until size).map { buffer[it] }.iterator()
override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
override fun toString(): String = this.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
}

View File

@ -51,6 +51,9 @@ interface Space<T> {
operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble())
operator fun Number.times(b: T) = b * this
//TODO move to external extensions when they are available
fun Iterable<T>.sum(): T = fold(zero) { left, right -> left + right }
fun Sequence<T>.sum(): T = fold(zero) { left, right -> left + right }
}
/**
@ -111,8 +114,8 @@ interface Field<T> : Ring<T> {
/**
* Field element
*/
interface FieldElement<T, S : Field<T>> : RingElement<T, S> {
override val context: S
interface FieldElement<T, F : Field<T>> : RingElement<T, F> {
override val context: F
operator fun div(b: T): T = context.divide(self, b)
}

View File

@ -5,11 +5,11 @@ import kotlin.math.pow
/**
* Advanced Number-like field that implements basic operations
*/
interface ExtendedField<N : Any> :
Field<N>,
TrigonometricOperations<N>,
PowerOperations<N>,
ExponentialOperations<N>
interface ExtendedField<T : Any> :
Field<T>,
TrigonometricOperations<T>,
PowerOperations<T>,
ExponentialOperations<T>
/**

View File

@ -11,10 +11,14 @@ interface Buffer<T> {
operator fun get(index: Int): T
operator fun iterator(): Iterator<T>
fun contentEquals(other: Buffer<*>): Boolean = asSequence().mapIndexed { index, value -> value == other[index] }.all { it }
}
fun <T> Buffer<T>.asSequence(): Sequence<T> = iterator().asSequence()
fun <T> Buffer<T>.asIterable(): Iterable<T> = iterator().asSequence().asIterable()
interface MutableBuffer<T> : Buffer<T> {
operator fun set(index: Int, value: T)
@ -95,6 +99,20 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> {
override fun copy(): MutableBuffer<Int> = IntBuffer(array.copyOf())
}
inline class LongBuffer(private val array: LongArray) : MutableBuffer<Long> {
override val size: Int get() = array.size
override fun get(index: Int): Long = array[index]
override fun set(index: Int, value: Long) {
array[index] = value
}
override fun iterator(): Iterator<Long> = array.iterator()
override fun copy(): MutableBuffer<Long> = LongBuffer(array.copyOf())
}
inline class ReadOnlyBuffer<T>(private val buffer: MutableBuffer<T>) : Buffer<T> {
override val size: Int get() = buffer.size
@ -112,26 +130,46 @@ fun <T> Buffer<T>.asReadOnly(): Buffer<T> = if (this is MutableBuffer) {
this
}
/**
* Create a boxing buffer of given type
*/
fun <T : Any> boxingBuffer(size: Int, initializer: (Int) -> T): Buffer<T> = ListBuffer(List(size, initializer))
/**
* Create most appropriate immutable buffer for given type avoiding boxing wherever possible
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> buffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> {
inline fun <reified T : Any> inlineBuffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> {
return when (T::class) {
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer<T>
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer<T>
else -> ArrayBuffer(Array(size, initializer))
Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer<T>
else -> boxingBuffer(size, initializer)
}
}
/**
* Create a boxing mutable buffer of given type
*/
fun <T : Any> boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer<T> = MutableListBuffer(MutableList(size, initializer))
/**
* Create most appropriate mutable buffer for given type avoiding boxing wherever possible
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> {
inline fun <reified T : Any> inlineMutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> {
return when (T::class) {
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer<T>
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer<T>
else -> ArrayBuffer(Array(size, initializer))
Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer<T>
else -> boxingMutableBuffer(size, initializer)
}
}
typealias BufferFactory<T> = (Int, (Int) -> T) -> Buffer<T>
typealias MutableBufferFactory<T> = (Int, (Int) -> T) -> MutableBuffer<T>
val DoubleBufferFactory: BufferFactory<Double> = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) }
val IntBufferFactory: BufferFactory<Int> = { size, initializer -> IntBuffer(IntArray(size, initializer)) }
val LongBufferFactory: BufferFactory<Long> = { size, initializer -> LongBuffer(LongArray(size, initializer)) }

View File

@ -9,33 +9,33 @@ import scientifik.kmath.operations.TrigonometricOperations
/**
* NDField that supports [ExtendedField] operations on its elements
*/
class ExtendedNDField<N : Any, F : ExtendedField<N>>(shape: IntArray, field: F) : NDField<N, F>(shape, field),
TrigonometricOperations<NDElement<N, F>>,
PowerOperations<NDElement<N, F>>,
ExponentialOperations<NDElement<N, F>> {
class ExtendedNDField<T : Any, F : ExtendedField<T>>(shape: IntArray, field: F) : NDField<T, F>(shape, field),
TrigonometricOperations<NDStructure<T>>,
PowerOperations<NDStructure<T>>,
ExponentialOperations<NDStructure<T>> {
override fun produceStructure(initializer: F.(IntArray) -> N): NDStructure<N> {
return ndStructure(shape) { field.initializer(it) }
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> {
return NdStructure(shape, ::boxingBuffer) { field.initializer(it) }
}
override fun power(arg: NDElement<N, F>, pow: Double): NDElement<N, F> {
return arg.transform { d -> with(field) { power(d, pow) } }
override fun power(arg: NDStructure<T>, pow: Double): NDElement<T, F> {
return produce { with(field) { power(arg[it], pow) } }
}
override fun exp(arg: NDElement<N, F>): NDElement<N, F> {
return arg.transform { d -> with(field) { exp(d) } }
override fun exp(arg: NDStructure<T>): NDElement<T, F> {
return produce { with(field) { exp(arg[it]) } }
}
override fun ln(arg: NDElement<N, F>): NDElement<N, F> {
return arg.transform { d -> with(field) { ln(d) } }
override fun ln(arg: NDStructure<T>): NDElement<T, F> {
return produce { with(field) { ln(arg[it]) } }
}
override fun sin(arg: NDElement<N, F>): NDElement<N, F> {
return arg.transform { d -> with(field) { sin(d) } }
override fun sin(arg: NDStructure<T>): NDElement<T, F> {
return produce { with(field) { sin(arg[it]) } }
}
override fun cos(arg: NDElement<N, F>): NDElement<N, F> {
return arg.transform { d -> with(field) { cos(d) } }
override fun cos(arg: NDStructure<T>): NDElement<T, F> {
return produce { with(field) { cos(arg[it]) } }
}
}

View File

@ -15,7 +15,7 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run
* @param field - operations field defined on individual array element
* @param T the type of the element contained in NDArray
*/
abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Field<NDElement<T, F>> {
abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Field<NDStructure<T>> {
abstract fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T>
@ -29,10 +29,14 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
produce { zero }
}
override val one: NDElement<T, F> by lazy {
produce { one }
}
/**
* Check the shape of given NDArray and throw exception if it does not coincide with shape of the field
*/
private fun checkShape(vararg elements: NDElement<T, F>) {
private fun checkShape(vararg elements: NDStructure<T>) {
elements.forEach {
if (!shape.contentEquals(it.shape)) {
throw ShapeMismatchException(shape, it.shape)
@ -43,7 +47,7 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
/**
* Element-by-element addition
*/
override fun add(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
override fun add(a: NDStructure<T>, b: NDStructure<T>): NDElement<T, F> {
checkShape(a, b)
return produce { with(field) { a[it] + b[it] } }
}
@ -51,18 +55,15 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
/**
* Multiply all elements by cinstant
*/
override fun multiply(a: NDElement<T, F>, k: Double): NDElement<T, F> {
override fun multiply(a: NDStructure<T>, k: Double): NDElement<T, F> {
checkShape(a)
return produce { with(field) { a[it] * k } }
}
override val one: NDElement<T, F>
get() = produce { one }
/**
* Element-by-element multiplication
*/
override fun multiply(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
override fun multiply(a: NDStructure<T>, b: NDStructure<T>): NDElement<T, F> {
checkShape(a)
return produce { with(field) { a[it] * b[it] } }
}
@ -70,7 +71,7 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
/**
* Element-by-element division
*/
override fun divide(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
override fun divide(a: NDStructure<T>, b: NDStructure<T>): NDElement<T, F> {
checkShape(a)
return produce { with(field) { a[it] / b[it] } }
}
@ -105,7 +106,7 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
}
interface NDElement<T, F : Field<T>>: FieldElement<NDElement<T, F>, NDField<T, F>>, NDStructure<T>
interface NDElement<T, F : Field<T>> : FieldElement<NDStructure<T>, NDField<T, F>>, NDStructure<T>
inline fun <T, F : Field<T>> NDElement<T, F>.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement<T, F> = context.produce { action(it, get(*it)) }
inline fun <T, F : Field<T>> NDElement<T, F>.transform(crossinline action: F.(T) -> T): NDElement<T, F> = context.produce { action(get(*it)) }
@ -167,7 +168,7 @@ operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> = tr
}
class GenericNDField<T : Any, F : Field<T>>(shape: IntArray, field: F) : NDField<T, F>(shape, field) {
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> = ndStructure(shape) { field.initializer(it) }
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> = NdStructure(shape, ::boxingBuffer) { field.initializer(it) }
}
//typealias NDFieldFactory<T> = (IntArray)->NDField<T>
@ -192,8 +193,10 @@ object NDArrays {
return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
}
inline fun produceReal(shape: IntArray, block: ExtendedNDField<Double, DoubleField>.() -> NDElement<Double, DoubleField>) =
ExtendedNDField(shape, DoubleField).run(block)
inline fun produceReal(shape: IntArray, block: ExtendedNDField<Double, DoubleField>.() -> NDStructure<Double>): NDElement<Double, DoubleField> {
val field = ExtendedNDField(shape, DoubleField)
return NDStructureElement(field, field.run(block))
}
/**
* Simple boxing NDArray

View File

@ -80,7 +80,7 @@ class DefaultStrides private constructor(override val shape: IntArray) : Strides
override fun offset(index: IntArray): Int {
return index.mapIndexed { i, value ->
if (value < 0 || value >= shape[i]) {
if (value < 0 || value >= this.shape[i]) {
throw RuntimeException("Index $value out of shape bounds: (0,${this.shape[i]})")
}
value * strides[i]
@ -138,24 +138,48 @@ class BufferNDStructure<T>(
error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}")
}
}
override fun equals(other: Any?): Boolean {
return when {
this === other -> true
other is BufferNDStructure<*> -> this.strides == other.strides && this.buffer.contentEquals(other.buffer)
other is NDStructure<*> -> elements().all { (index, value) -> value == other[index] }
else -> false
}
}
override fun hashCode(): Int {
var result = strides.hashCode()
result = 31 * result + buffer.hashCode()
return result
}
}
/**
* Create a most suitable nd-structure avoiding boxing if possible using given strides.
* Create a NDStructure with explicit buffer factory
*
* Strides should be reused if possible
*/
inline fun <reified T : Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
BufferNDStructure<T>(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) })
@Suppress("FunctionName")
fun <T : Any> NdStructure(strides: Strides, bufferFactory: BufferFactory<T>, initializer: (IntArray) -> T) =
BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) })
/**
* Create a most suitable nd-structure avoiding boxing if possible using default strides with given shape
* Inline create NDStructure with non-boxing buffer implementation if it is possible
*/
inline fun <reified T : Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
ndStructure(DefaultStrides(shape), initializer)
inline fun <reified T : Any> inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) =
BufferNDStructure(strides, inlineBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
@Suppress("FunctionName")
fun <T : Any> NdStructure(shape: IntArray, bufferFactory: BufferFactory<T>, initializer: (IntArray) -> T) =
NdStructure(DefaultStrides(shape), bufferFactory, initializer)
inline fun <reified T : Any> inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) =
inlineNDStructure(DefaultStrides(shape), initializer)
/**
* Mutable ND buffer based on linear [Buffer]
* Mutable ND buffer based on linear [inlineBuffer]
*/
class MutableBufferNDStructure<T>(
override val strides: Strides,
@ -172,13 +196,27 @@ class MutableBufferNDStructure<T>(
}
/**
* The same as [ndStructure], but mutable
* The same as [inlineNDStructure], but mutable
*/
inline fun <reified T : Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
@Suppress("FunctionName")
fun <T : Any> MutableNdStructure(strides: Strides, bufferFactory: MutableBufferFactory<T>, initializer: (IntArray) -> T) =
MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) })
inline fun <reified T : Any> inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) =
MutableBufferNDStructure(strides, inlineMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
@Suppress("FunctionName")
fun <T : Any> MutableNdStructure(shape: IntArray, bufferFactory: MutableBufferFactory<T>, initializer: (IntArray) -> T) =
MutableNdStructure(DefaultStrides(shape), bufferFactory, initializer)
inline fun <reified T : Any> inlineMutableNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) =
inlineMutableNdStructure(DefaultStrides(shape), initializer)
inline fun <reified T : Any> NDStructure<T>.combine(struct: NDStructure<T>, crossinline block: (T, T) -> T): NDStructure<T> {
if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination")
return inlineNdStructure(shape) { block(this[it], struct[it]) }
}
inline fun <reified T : Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
mutableNdStructure(DefaultStrides(shape), initializer)
///**
// * Create universal mutable structure

View File

@ -1,34 +0,0 @@
package scientifik.kmath.linear
import kotlin.test.Test
import kotlin.test.assertEquals
class ArrayMatrixTest {
@Test
fun testSum() {
val vector1 = Vector.ofReal(5) { it.toDouble() }
val vector2 = Vector.ofReal(5) { 5 - it.toDouble() }
val sum = vector1 + vector2
assertEquals(5.0, sum[2])
}
@Test
fun testVectorToMatrix() {
val vector = Vector.ofReal(5) { it.toDouble() }
val matrix = vector.toMatrix()
assertEquals(4.0, matrix[4, 0])
}
@Test
fun testDot() {
val vector1 = Vector.ofReal(5) { it.toDouble() }
val vector2 = Vector.ofReal(5) { 5 - it.toDouble() }
val product = vector1.toMatrix() dot (vector2.toMatrix().transpose())
assertEquals(5.0, product[1, 0])
assertEquals(6.0, product[2, 2])
}
}

View File

@ -0,0 +1,46 @@
package scientifik.kmath.linear
import kotlin.test.Test
import kotlin.test.assertEquals
class MatrixTest {
@Test
fun testSum() {
val vector1 = Vector.real(5) { it.toDouble() }
val vector2 = Vector.real(5) { 5 - it.toDouble() }
val sum = vector1 + vector2
assertEquals(5.0, sum[2])
}
@Test
fun testVectorToMatrix() {
val vector = Vector.real(5) { it.toDouble() }
val matrix = vector.toMatrix()
assertEquals(4.0, matrix[4, 0])
}
@Test
fun testTranspose(){
val matrix = MatrixSpace.real(3,3).one
val transposed = matrix.transpose()
assertEquals(matrix.context, transposed.context)
assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure)
assertEquals(matrix, transposed)
}
@Test
fun testDot() {
val vector1 = Vector.real(5) { it.toDouble() }
val vector2 = Vector.real(5) { 5 - it.toDouble() }
val matrix1 = vector1.toMatrix()
val matrix2 = vector2.toMatrix().transpose()
val product = matrix1 dot matrix2
assertEquals(5.0, product[1, 0])
assertEquals(6.0, product[2, 2])
}
}

View File

@ -1,15 +1,14 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.DoubleField
import kotlin.test.Test
import kotlin.test.assertTrue
import kotlin.test.assertEquals
class RealLUSolverTest {
@Test
fun testInvertOne() {
val matrix = Matrix.diagonal(2, 2, DoubleField)
val matrix = MatrixSpace.real(2,2).one
val inverted = RealLUSolver.inverse(matrix)
assertTrue { Matrix.equals(matrix,inverted) }
assertEquals(matrix,inverted)
}
// @Test

View File

@ -6,7 +6,6 @@ import kotlin.test.assertEquals
class RealFieldTest {
@Test
fun testSqrt() {
//fails because KT-27586
val sqrt = with(RealField) {
sqrt( 25 * one)

View File

@ -51,9 +51,14 @@ class NumberNDFieldTest {
assertEquals(2.0, result[0, 2])
}
object L2Norm : Norm<NDElement<out Number, *>, Double> {
override fun norm(arg: NDElement<out Number, *>): Double {
return kotlin.math.sqrt(arg.sumByDouble { it.second.toDouble() })
@Test
fun combineTest(){
val division = array1.combine(array2, Double::div)
}
object L2Norm : Norm<NDStructure<out Number>, Double> {
override fun norm(arg: NDStructure<out Number>): Double {
return kotlin.math.sqrt(arg.elements().sumByDouble { it.second.toDouble() })
}
}