You are currently viewing Multik: Multidimensional Arrays in Kotlin

Multik: Multidimensional Arrays in Kotlin

A lot of data-heavy tasks, as well as optimization problems, boil down to performing computations over multidimensional arrays. Today we’d like to share with you the first preview of a library that aims to serve as a foundation for such computations – Multik.

Multik offers both multidimensional array data structures and implementations of mathematical operations over them. The library has a simple and straightforward API and offers optimized performance.

Using Multik

Without further ado, here are some of the things you can do with Multik.

Create multidimensional arrays

Create a vector:

val a = mk.ndarray(mk[1, 2, 3])
/* [1, 2, 3] */

Create a vector from a collection:

val myList = listOf(1, 2, 3)
val a = mk.ndarray(myList)
/* [1, 2, 3] */

Create a matrix (two-dimensional array):

val m = mk.ndarray(mk[myList, myList])
/*
[[1, 2, 3],
[1, 2, 3]]
*/

Create a fixed-shape array of zeros:

mk.empty<Double, D2>(3, 4)
/*
[[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0]]
*/

Create an identity matrix (ones on the diagonal, the rest is set to 0)

val e = mk.identity<Double>(3) // create an identity array of shape (3, 3)
/*
[[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]]
*/

Create a 3-dimensional array (multik supports up to 4 dimensions):

mk.d3array(2, 2, 3) { it * it } 
/*
[[[0, 1, 4],
[9, 16, 25]],

[[36, 49, 64],
[81, 100, 121]]]
*/

Perform mathematical operations over multidimensional arrays

val a = mk.ndarray(mk[mk[1.0,2.0], mk[3.0,4.0]])
val b = mk.identity<Double>(2)

a + b
/*
[[2.0, 2.0],
[3.0, 5.0]]
*/
a - b
/*
[[0.0, 2.0],
[3.0, 3.0]]
*/
b / a
/*
[[1.0, 0.0],
[0.0, 0.25]]
*/
b * a 
/*
[[1.0, 0.0],
[0.0, 4.0]]
*/

Element-wise mathematical operations

mk.math.sin(a) // element-wise sin 
mk.math.cos(a) // element-wise cos
mk.math.log(b) // element-wise natural logarithm
mk.math.exp(b) // element-wise exp
mk.linalg.dot(a, b) // dot product

Aggregate functions

mk.math.sum(a) // array-wise sum
mk.math.min(b) // array-wise minimum elements
mk.math.cumSum(b, axis=1) // cumulative sum of the elements
mk.stat.mean(a) // mean
mk.stat.median(b) // median

Iterable operations

a.filter { it > 3 } // select all elements that are larger than 3
b.map { (it * it).toInt() } // return squares
a.groupNdarrayBy { it % 2 } // group elements by condition
a.sorted() // sort elements

Indexing/Slicing/Iterating

a[2]  // select the element at the 2 index for a vector
b[1, 2] // select the element at row 1 column 2
b[1] // select row 1 
b[0.r..2, 1] // select elements at rows 0 and 1 in column 1
b[0..1..1] // select all elements at row 0
for (el in b) {
    print("$el, ") // 1.5, 2.1, 3.0, 4.0, 5.0, 6.0, 
}
// for n-dimensional
val q = b.asDNArray()
for (index in q.multiIndices) {
    print("${q[index]}, ") // 1.5, 2.1, 3.0, 4.0, 5.0, 6.0, 
}

Multik Architecture

Initially, we attempted to add Kotlin bindings to existing solutions, such as NumPy. However, this proved cumbersome and introduced unnecessary environmental complexity while providing little benefit to justify the overhead. As a result, we have abandoned that approach and started Multik from scratch.

In Multik, the data structures are separate from the implementation of operations over them, and you need to add them as individual dependencies to your project. This approach gives you a consistent API no matter what implementation you decide to use in your project. So what are these different implementations?

Currently, there are three different ones:

  • multik-jvm: a Kotlin/JVM implementation of the math operations.
  • multik-native: a C++ implementation. OpenBLAS is used for linear algebra.
  • multik-default: the default implementation, which combines native and JVM implementations for optimal performance.

You can also write your own!

Multik is still in the early stages of development, and we are looking forward to your feedback, feature requests, and contributions! Check out the project’s GitHub repo, try Multik, and let us know what you’d like to see in future versions. Thanks!