Auto Added by WPeMatico

Multik 0.2: Multiplatform, With Support for Android and Apple Silicon

Introducing Multik 0.2.0! Now a multiplatform library, it allows you to use multidimensional arrays in your favorite multiplatform projects. Let’s take a closer look at what’s new in v0.2.0.

Multik on GitHub

Multiplatform

We are very grateful to Luca Spinazzola for his huge contribution to the multiplatform capabilities included in this release of the library.

Before we move on to reviewing Multik’s new multiplatform structure, we need to say a few words about the new naming conventions. Ever since we multiplied the number of artifacts and added platform suffixes, such as jvm, macosx64, js, and others, there have been collisions with older names. To solve this problem, we’ve renamed some of the modules.

Let’s reacquaint ourselves with the modules and get a sense of which platforms each module now supports.

multik-core

As the name suggests, this is the main module providing multidimensional arrays and the ability to perform transformations, iteration, and arithmetic operations with them. This module also provides an API for calculations that require complex algorithms and resources. Now there are three types of such APIs: mathematics, linear algebra, and statistics. Other modules are responsible for the implementation of this API. Remember – Multik lets you replace these implementations at runtime.

We have added support for all major platforms for this module. Note that JavaScript uses a new IR and Kotlin/Native uses a new memory model, so these artifacts will only be compatible with projects that support them.

multik-core multik-kotlin multik-openblas multik-default
jvm linuxX64
mingwX64
macosX64
macosArm64
androidArm64
linuxX64
mingwX64
macosX64
macosArm64
androidArm64
linuxX64
mingwX64
macosX64
macosArm64
iosArm64
iosX64
iosSimulatorArm64
js
other platforms are not supported

multik-kotlin

The first module that implements the above API is multik-kotlin. In this module, all algorithms and logic are written in pure Kotlin. Even though it may be slower than native libraries, it provides more stability and allows for easier code debugging.

Because everything is written in Kotlin, it was also possible to support most of the important platforms, including JVM, Desktop, iOS, and JavaScript.

multik-openblas

The next module is multik-openblas. Here, the OpenBLAS library is responsible for all linear algebra as well as the C wrapper over the Fortran libraries LAPACK and BLAS. C++ code is responsible for mathematics and statistics.

This module, unlike the previous one, is quite demanding on the environment and the platform it’s launched on. In the table, you can see that the code under the JVM will only work on the specified systems and architectures. On these platforms, we ensure that it works out of the box and the users are rewarded with excellent performance.

multik-default

multik-default, the last of the 4 models available at the moment, has kept its old name. It includes the two previous modules, multik-kotlin and multik-openblas. The idea is to combine the pros of both modules while doing away with the cons.

It supports all of the same platforms as the previous modules.

Support for Android and Apple Silicon processors

multik-openblas is supported by Android and macOS on new Apple processors. Now you can enjoy the speed of applications on Android with ARMv8 processors and native support for M1 and M2 processors from Apple.

Random, norm matrix, easy creation of complex numbers, and more

In this release, we have also improved the usability of the library. For example, we wrapped random from Kotlin to create arrays with random numbers:

val ndarray = mk.rand<Float>(3, 5, 2)

We have changed the matrix norm calculation function and added it to the native one:

val ndarray = mk.ndarray(mk[mk[1.0, 2.0], mk[3.0, 4.0]])
mk.linalg.norm(ndarray)
mk.linalg.norm(ndarray, Norm.Inf)

And now you can create complex numbers easily and naturally. Credit for this contribution goes to Marcus Dunn.

val complexNumber: ComplexDouble = 1.0 + 1.0.i

For more details about this new release, please check out the changelog.

How to try it

To try Multik 0.2.0 in your project, do the following:

  • Make sure that you have mavenCentral() in your list of repositories:
repository {
    mavenCentral()
}
  • Add the Multik module you need as a dependency:
dependencies {
    implementation("org.jetbrains.kotlinx:multik-core:0.2.0")
}

For a multiplatform project, put the Multik dependency in the common set:

commonMain{
   dependencies {
       implementation("org.jetbrains.kotlinx:multik-core:0.2.0")
   }
}

Or put the dependency in a specific source set.

Multik is also available in Kotlin Jupyter notebooks.

%use multik

Try it in Datalore.

Conclusion

We are on our way to a stable release and could really use your feedback.

Try out Mutlik 0.2.0 and share your experience with us! Report any issues you encounter to the project’s issue tracker.

Read more

Continue ReadingMultik 0.2: Multiplatform, With Support for Android and Apple Silicon

Multik 0.2: Multiplatform, With Support for Android and Apple Silicon

Introducing Multik 0.2.0! Now a multiplatform library, it allows you to use multidimensional arrays in your favorite multiplatform projects. Let’s take a closer look at what’s new in v0.2.0.

Multik on GitHub

Multiplatform

We are very grateful to Luca Spinazzola for his huge contribution to the multiplatform capabilities included in this release of the library.

Before we move on to reviewing Multik’s new multiplatform structure, we need to say a few words about the new naming conventions. Ever since we multiplied the number of artifacts and added platform suffixes, such as jvm, macosx64, js, and others, there have been collisions with older names. To solve this problem, we’ve renamed some of the modules.

Let’s reacquaint ourselves with the modules and get a sense of which platforms each module now supports.

multik-core

As the name suggests, this is the main module providing multidimensional arrays and the ability to perform transformations, iteration, and arithmetic operations with them. This module also provides an API for calculations that require complex algorithms and resources. Now there are three types of such APIs: mathematics, linear algebra, and statistics. Other modules are responsible for the implementation of this API. Remember – Multik lets you replace these implementations at runtime.

We have added support for all major platforms for this module. Note that JavaScript uses a new IR and Kotlin/Native uses a new memory model, so these artifacts will only be compatible with projects that support the new IR and new memory model, respectively.

multik-core multik-kotlin multik-openblas multik-default
jvm linuxX64
mingwX64
macosX64
macosArm64
androidArm64
linuxX64
mingwX64
macosX64
macosArm64
androidArm64
linuxX64
mingwX64
macosX64
macosArm64
iosArm64
iosX64
iosSimulatorArm64
js
other platforms are not supported

multik-kotlin

The first module that implements the above API is multik-kotlin. In this module, all algorithms and logic are written in pure Kotlin. Even though it may be slower than native libraries, it provides more stability and allows for easier code debugging.

Because everything is written in Kotlin, it was also possible to support most of the important platforms, including JVM, Desktop, iOS, and JavaScript.

multik-openblas

The next module is multik-openblas. Here, the OpenBLAS library is responsible for all linear algebra as well as the C wrapper over the Fortran libraries LAPACK and BLAS. C++ code is responsible for mathematics and statistics.

This module, unlike the previous one, is quite demanding on the environment and the platform it’s launched on. In the table, you can see that the code under the JVM will only work on the specified systems and architectures. On these platforms, we ensure that it works out of the box and the users are rewarded with excellent performance.

multik-default

multik-default, the last of the 4 models available at the moment, has kept its old name. It includes the two previous modules, multik-kotlin and multik-openblas. The idea is to combine the pros of both modules while doing away with the cons.

It supports all of the same platforms as the previous modules.

Support for Android and Apple Silicon processors

multik-openblas is supported by Android and macOS on new Apple processors. Now you can enjoy the speed of applications on Android with ARMv8 processors and native support for M1 and M2 processors from Apple.

Random, norm matrix, easy creation of complex numbers, and more

In this release, we have also improved the usability of the library. For example, we wrapped random from Kotlin to create arrays with random numbers:

val ndarray = mk.rand<Float>(3, 5, 2)

We have changed the matrix norm calculation function and added it to the native one:

val ndarray = mk.ndarray(mk[mk[1.0, 2.0], mk[3.0, 4.0]])
mk.linalg.norm(ndarray)
mk.linalg.norm(ndarray, Norm.Inf)

And now you can create complex numbers easily and naturally. Credit for this contribution goes to Marcus Dunn.

val complexNumber: ComplexDouble = 1.0 + 1.0.i

For more details about this new release, please check out the changelog.

How to try it

To try Multik 0.2.0 in your project, do the following:

  • Make sure that you have mavenCentral() in your list of repositories:
repository {
    mavenCentral()
}
  • Add the Multik module you need as a dependency:
dependencies {
    implementation("org.jetbrains.kotlinx:multik-core:0.2.0")
}

For a multiplatform project, put the Multik dependency in the common set:

commonMain{
   dependencies {
       implementation("org.jetbrains.kotlinx:multik-core:0.2.0")
   }
}

Or put the dependency in a specific source set.

Multik is also available in Kotlin Jupyter notebooks.

%use multik

Try it in Datalore.

Conclusion

We are on our way to a stable release and could really use your feedback.

Try out Mutlik 0.2.0 and share your experience with us! Report any issues you encounter to the project’s issue tracker.

Read more

Continue ReadingMultik 0.2: Multiplatform, With Support for Android and Apple Silicon

Introducing kotlinx.coroutines 1.6.0

Following the release of Kotlin 1.6.0, the 1.6.0 version of the kotlinx.coroutines library is out. Here are the main features it brings:

In this blog post, we’ll take a closer look at all the new features. To try them out right away, jump to the How to try it section.

A new API and multiplatform support for kotlinx-coroutines-test

Following our roadmap, we completely reworked kotlinx-coroutines-test. The testing module received multiplatform support and solved the problem of writing portable tests with suspending functions, which we decided to shift into the library space. The new experimental API also fixed multiple issues with the previously used runBlockingTest() scheme.

The entry point to the new API is the runTest() function, which you can use on any platform to test code that involves coroutines. runTest() will automatically skip calls to delay() and handle uncaught exceptions. Unlike runBlockingTest(), it will wait for asynchronous callbacks to handle situations where some code runs in dispatchers that are not integrated with the test module.

Call runTest() only once per test and immediately return its result. This restriction is necessary for the test to work on the JS platform. If for some reason you need to call runTest() several times, please share your use case using the issue tracker.

@Test
fun testF() = runTest { // Run a coroutine with virtual time
   val actual = f()
   val expected = 42
   assertEquals(actual, expected)
}

suspend fun f(): Int {
   delay(1_000) // Will finish immediately instead of delaying
   return 42
}

You can find a detailed description of the API in the module’s README. To adapt the existing test code to the new scheme, please follow the migration guide. The old API has now been deprecated.

Support for the new Kotlin/Native memory manager

The GitHub issue about supporting multithreaded coroutines for Kotlin/Native has received a huge number of upvotes. In 1.3.2, we started maintaining a companion library version that included the feature’s experimental implementation within the regular Kotlin/Native memory model. Since then, we have started publishing a separate native-mt artifact for each release to share this implementation.

With Kotlin 1.6.0, we announced the new experimental Kotlin/Native memory management scheme, which made the limitations of the existing native-mt version possible to overcome. In this release, we implemented support for the new memory manager and merged the implementation into the mainline. This means you only need the 1.6.0 version of kotlinx.coroutines to try experimental Kotlin/Native multithreading with the new memory model.

Since the old native-mt implementation still suffers from memory leaks in some concurrent execution scenarios, we are going to decommission it starting with kotlinx.coroutines 1.7.0. For the 1.6.* releases, native-mt artifacts will still be published. You can migrate to the new memory management scheme by following the migration guide.

Dispatcher views API

You might want to control concurrency while using coroutines, for example, to limit the number of concurrent requests to a database. A popular solution for this is to define a custom coroutine dispatcher with the newFixedThreadPoolContext() function which is then used on every database invocation:

class UserRepository {
   private val DB = newFixedThreadPoolContext(10, "DB")

   suspend fun getUserById(userId: Int): User? = withContext(DB) {
       executeQuery("SELECT * FROM users WHERE id = $1", userId).singleOrNull()
   }
}

Unfortunately, this approach has several problems: 

  • newFixedThreadPoolContext() can create many unnecessary threads, most of which are idle, consuming the memory, CPU, and device battery.
  • Every withContext(DB) invocation performs an actual switch to a different thread, which can be extremely resource intensive.
  • The result of newFixedThreadPoolContext() needs to be explicitly closed when no longer used. This is quite error-prone, often forgotten about, and can lead to leaking threads.
  • If you have several thread pools as separate executors, they cannot share threads and resources.

In kotlinx.coroutines 1.6.0, we’ve introduced the new dispatcher views API as an option to limit concurrency without creating additional threads and allocating extra resources. To start using dispatcher views, just call limitedParallelism() instead of newFixedThreadPoolContext().

class UserRepository {
   private val DB = Dispatchers.IO.limitedParallelism(10)

   suspend fun getUserById(userId: Int): User? = withContext(DB) {
       executeQuery("SELECT * FROM users WHERE id = $1", userId).singleOrNull()
   }
}

The new approach addresses the limitations of using custom thread pools:

  • A dispatcher view is only a wrapper to the original dispatcher. Using the original dispatcher’s resources, it limits the number of coroutines that can be executed simultaneously and doesn’t create new threads.
  • The withContext() invocation with Dispatchers.Default, Dispatchers.IO, or their views attempts not to switch threads when possible.
  • A view doesn’t need to be closed.
  • To create separate executors, you can take multiple views of the same dispatcher and they will share threads and resources. There is no limit on the total parallelism value, but the effective parallelism of all views cannot exceed the actual parallelism of the original dispatcher. This means that you can control the parallelism of both the entire application and each view separately.
val backgroundDispatcher = newFixedThreadPoolContext(5, "App Background")
// At most 2 threads will process images
val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(2)
// At most 1 thread will do IO
val fileWriterDispatcher = backgroundDispatcher.limitedParallelism(1)
// At most 3 threads will handle database requests 
val dbDispatcher = backgroundDispatcher.limitedParallelism(3)

// At most 5 coroutines can be executed simultaneously

Dispatchers.IO elasticity for limited parallelism

Imagine a case where your application uses multiple views as separate executors and needs each one to guarantee a specified level of parallelism during peak loads. You don’t need to create a parent dispatcher with the desired total parallelism value. Instead, you can use views of Dispatchers.IO which can create and shutdown additional threads on-demand, saving resources in a steady state.

The implementation of limitedParallelism() for Dispatchers.IO is elastic. This means that Dispatchers.IO itself still has a limit of 64 threads, but each of its views will have the effective parallelism of the requested value. 

// 80 threads for PostgreSQL connection
val myPostgresqlDbDispatcher = Dispatchers.IO.limitedParallelism(80)
// 40 threads for MongoDB connection
val myMongoDbDispatcher = Dispatchers.IO.limitedParallelism(40)

In the example:

  • During peak loads, the system may have up to 64 + 80 + 40 threads dedicated to blocking tasks. 
  • In a steady state, there will be only a small number of threads shared among Dispatchers.IO, myPostgresqlDbDispatcher, and myMongoDbDispatcher.

Under the hood, it works with an additional dispatcher backed by an unlimited pool of threads. Both Dispatchers.IO and its views are actually views of that dispatcher and share threads and resources.

Introduction of CopyableThreadContextElement

In Java, you can use a ThreadLocal variable to maintain some value related to the current thread. In kotlinx.coroutines, the same can be achieved with ThreadContextElement. However, since ThreadContextElement is a part of CoroutineContext, it gets inherited by child coroutines, which can execute concurrently. This can be a problem if the underlying value is not thread-safe and gets concurrently mutated, for example, when implementing logging contexts or tracing frameworks. 

To resolve the issue, we created the new CopyableThreadContextElement interface, which defines an extra copyForChildCoroutine() method. During the child coroutine’s launch time, the method will be called on the parent ThreadContextElement instance to obtain a new ThreadContextElement for the child coroutine’s context. Thus, every coroutine will have its own element copy that it can mutate, guaranteeing thread safety for the underlying value.

Migration to the Java 8 target

Maintaining support for the Java 6 target requires a lot of work and prevents us from using potentially helpful features that Java 8 offers. Kotlin already migrated to Java 8 in version 1.5.0. Catching up with the language, kotlinx.coroutines 1.6.0 has begun the migration process by compiling against the Java 8 source and binary targets.

Although support for the Java 6 target has been dropped, kotlinx.coroutines 1.6.0 remains compatible with popular Android API levels and uses new features of Java 7+ only if there is a way to provide a proper fallback.

How to try it

kotlinx.coroutines 1.6.0 is available from Maven Central. To try it in your project, do the following:

  • Make sure that you have mavenCentral() in the list of repositories:
repository {
    mavenCentral()
}
  • Make sure that you are using the latest version of Kotlin:
plugins {
    kotlin("jvm") version "1.6.0"
}
  • Add kotlinx.coroutines as a dependency:
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
}

If you run into any trouble

Read more

Continue ReadingIntroducing kotlinx.coroutines 1.6.0

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

Multik 0.1 Is Out

Introducing Multik 0.1 – a new, enhanced version of our multidimensional array library! You can check out the previous post to learn about the basic features and architecture of the library.

In the new release, we added new methods from linear algebra, supported complex numbers and reading/writing .csv files, improved the performance and stability of existing functions, and added many more features that will make it easier for you to work with multidimensional arrays.

Multik on GitHub

Let’s take a look at the new features this release brings to the API:

Reading and writing CSV files

CSV is a popular data recording format, and now Multik 0.1 allows you to read and write .csv files easily.

val a = mk.d2array(2, 2) { it }
mk.write("example.csv", a)
val b = mk.read<Int, D2>("example.csv")
println(b)
/*
[[0, 1],
[2, 3]]
*/

Complex numbers

Complex numbers are expressed as re + i ∙ im, where re and im are real numbers and i is the imaginary unit equal to the square root of -1; re is the real part of the complex number and i ∙ im is the imaginary part. Complex numbers are common in algebra and are now a part of the Multik API.

val cf = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val cd = ComplexDouble.one // 1.0+(0.0)i

The ComplexFloat and ComplexDouble classes are created in full accordance with the Number classes, so there is no need to learn a new API.

val c1 = ComplexFloat(1f, 2f) // 1.0+(2.0)i
val c2 = ComplexFloat(3f, 4f) // 3.0+(4.0)i

println(c1 + c2) // 4.0+(6.0)i
println(c1 * c2) // -5.0+(10.0)i

val (re, im) = c1 // re = 1.0; im = 2.0
val re2 = c2.re // 3.0
val im2 = c2.im // 4.0

We’ve also added ComplexFloatArrays and ComplexDoubleArrays. They support all methods that primitive arrays do, except for methods that use comparison, since complex numbers are unordered. But you can use methods such as minBy, maxBy, sortedBy, and others to define the comparison logic yourself.

val array = ComplexDoubleArray(5) { ComplexDouble(it, it.toDouble() / 2) }

println(array)
// [0.0 + 0.0i, 1.0 + 0.0i, 2.0 + 1.0i, 3.0 + 1.0i, 4.0 + 2.0i]

println(array.filter { it.abs() > 2.0 })
// [2.0+(1.0)i, 3.0+(1.5)i, 4.0+(2.0)i]

println(array.sortedByDescending { it.im })
// [4.0+(2.0)i, 3.0+(1.5)i, 2.0+(1.0)i, 1.0+(0.5)i, 0.0+(0.0)i]

After supporting complex numbers and arrays of complex numbers, we added them to multidimensional arrays.

println(mk.d2arrayIndices(2, 2) { i, j -> ComplexFloat(i, j) })
/*
[[0.0+(0.0)i, 0.0+(1.0)i],
[1.0+(0.0)i, 1.0+(1.0)i]]
 */

println(mk.empty<ComplexDouble, D1>(5))
// [0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i, 0.0+(0.0)i]

println(mk.ndarrayOf(ComplexDouble.zero, ComplexDouble.one))
//[0.0+(0.0)i, 1.0+(0.0)i]

You can do everything with them that you did with integer and float multidimensional arrays before.

val ndarray = mk.d2arrayIndices(3, 3) { i, j -> ComplexDouble(i, j) }
println(ndarray.map { it.re * it.re + it.im * it.im })
/*
[[0.0, 1.0, 4.0],
[1.0, 2.0, 5.0],
[4.0, 5.0, 8.0]]
 */

 println(mk.math.sin(ndarray))
 /*
[[0.0+(0.0)i, 0.0+(1.1752011936438014)i, 0.0+(3.626860407847019)i],
[0.8414709848078965+(0.0)i, 1.2984575814159773+(0.6349639147847361)i, 3.165778513216168+(1.9596010414216063)i],
[0.9092974268256817+(-0.0)i, 1.4031192506220405+(-0.4890562590412937)i, 3.4209548611170133+(-1.5093064853236158)i]]
*/

We still have several open tasks for complex numbers. You can participate in the development of the library and become a contributor.

LU factorization

LU factorization, or lower-upper decomposition, represents a matrix as the product of a permutation matrix, a lower triangular matrix, and an upper triangular matrix: A = P ✕ L ✕ U.

We have Introduced this in Multik.

Figure 1. PLU factorization.
val a = mk.d2array(3, 3) { it.toDouble() }
val (p, l, u) = mk.linalg.plu(a)

abs((p dot l dot u) - a).all { 1e-13 > it } // true

Solving linear systems

Solving systems of linear equations is a common problem encountered in algebra. With PLU factorization, we can easily solve square linear systems. To do this, we have provided a corresponding method in linalg.

val a = mk.ndarray(mk[mk[2, 5, 1], mk[7, 3, 1], mk[8, 9, 4]])
val b = mk.ndarray(mk[3, 1, 2])
mk.linalg.solve(a, b)
// [-0.036363636363636265, 0.9090909090909091, -1.4727272727272729]

Inverse matrix

The inverse matrix of a square matrix A, denoted A-1, is such that the following equality holds: A ✕ A-1 = A-1 ✕ A = I, where I is the square identity matrix. If A-1 is nonsingular, then it can be calculated by solving the above expression. For convenience, we have added this method out of the box.

val a = mk.ndarray(mk[mk[2.0, 5.0, 1.0], mk[7.0, 3.0, 1.0], mk[8.0, 9.0, 4.0]])
val ainv = mk.linalg.inv(a)
abs((a dot ainv) - mk.identity<Double>(3)).all { 1e-13 > it } // true

QR factorization

Another important decomposition is QR decomposition. We can decompose any square matrix A as a product Q ✕ R, where Q is an orthogonal matrix and R is an upper triangular matrix.

val a = mk.d2array(3, 3) { (it * it).toDouble() }
val (q, r) = mk.linalg.qr(a)
abs(a - (q dot r)).all { 1e-13 > it } //  true

Eigenvalues and eigenvectors

Based on the QR decomposition, we’ve made it possible to calculate eigenvalues and eigenvectors with Multik.

val a = mk.ndarray(mk[mk[1, -1], mk[1, 1]])
val (w, v) = mk.linalg.eig(a)
/*
w = [0.9999999999999998+(0.9999999999999998)i, 1.0+(-0.9999999999999998)i]

v = 
[[0.7071067811865476+(0.0)i, 0.7071067811865474+(0.0)i],
[-0.0+(-0.7071067811865475)i, -8.326672684688674E-17+(0.7071067811865474)i]]
*/

Append, stack, and meshgrid

In this release, we have added functions to help you work with multidimensional arrays more easily.

For example, you can use the append function to concatenate an array with scalars and other arrays. We’re grateful to our contributor Ansh Tyagi for implementing this feature.

Figure 2. Appending two vectors.
val a = mk.d1array(5) { it }
a.append(10) // [0, 1, 2, 3, 4, 10]
a.append(mk.ndarrayOf(9, 8, 7)) // [0, 1, 2, 3, 4, 9, 8, 7]

Append can also be used along the ndarray axes, which then becomes equivalent to cat function.

Figure 3. Appending two matrices along the axis.
val a = mk.d2array(2, 3) { it }
println(a.append(a, axis=0))
/*
[[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]]
*/

println(a.append(a, axis=1))
/*
[[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]]
*/

stack allows arrays to be concatenated along a new axis.

Figure 4. Stacking two vectors.
val a = mk.ndarrayOf(1, 2, 3)
println(mk.stack(a, a))
/*
[[1, 2, 3],
[1, 2, 3]]
*/

println(mk.stack(a, a, axis=1))
/*
[[1, 1],
[2, 2],
[3, 3]]
*/

And meshgrid allows you to get a grid of coordinates from vectors.

val x = mk.linspace<Double>(0, 1, 3)
val y = mk.linspace<Double>(0, 1, 4)
val (xg, yg) = mk.meshgrid(x, y)

println(xg)
/*
[[0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0],
 [0.0, 0.5, 1.0]]
 */

println(yg)
/*
[[0.0, 0.0, 0.0],
 [0.3333333333333333, 0.3333333333333333, 0.3333333333333333],
 [0.6666666666666666, 0.6666666666666666, 0.6666666666666666],
 [1.0, 1.0, 1.0]]
 */

Conclusion

For more details about this new release, please check out the changelog.

We want to thank our users and contributors – you help us improve the library and make it more reliable and convenient!

To use Multik in your project, add the following dependencies to your build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlinx:multik-api:0.1.0"
    implementation "org.jetbrains.kotlinx:multik-default:0.1.0"
}

You can use different engines, not just the default. We have multik-default, multik-native, and multik-jvm. Please note that on Android we only support the JVM engine.

Alternatively, you can start using Multik in Jupyter or Datalore:

%use multik

If you don’t have the kotlin-kernel for Jupyter, you can read about installation here. In Datalore, everything works out of the box.

We’re looking for contributions from the community, so please don’t hesitate to join the effort! Current tasks can always be found in the issue tracker.

Try out Multik 0.1 and share your experience with us!

Let’s Kotlin!

Continue ReadingMultik 0.1 Is Out

End of content

No more pages to load