diff --git a/README.md b/README.md index 863a3e826..13db36602 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ -# kmath -Kotlin mathematics extensions library +# KMath +Kotlin MATHematics library is intended as a kotlin based analog of numpy python library. Contrary to `numpy` +and `scipy` it is modular and has a lightweight core. + +## Features + +* **Algebra** + * Mathematical operation entities like rings, spaces and fields with (**TODO** add example to wiki) + * Basic linear algebra operations (summs products, etc) backed by `Space` API. + * [In progress] advanced linear algebra operations like matrix inversions. +* **Array-like structures** Full support of numpy-like ndarray including mixed ariphmetic operations and function operations +on arrays and numbers just like it works in python (with benefit of static type checking). + +## Multi-platform support +KMath is developed as a multi-platform library, which means that most of interfaces are declared in common module. +Implementation is also done in common module wherever it is possible. In some cases features are delegated to +platform even if they could be done in common module because of platform performance optimization. + +## Performance +The calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve +both performance and flexibility. We expect to firstly focus on creating convenient universal API and then work on +increasing performance for specific cases. We expect the worst KMath performance still be better than natural python, +but worse than optimized native/scipy (mostly due to boxing operations on primitive numbers). The best performance +of optimized parts should be better than scipy. + +## Releases +The project is currently in pre-release stage. Work builds could be obtained with +[![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). + +## Contributing +The project requires a lot of additional work. Please fill free to contribute in any way and propose new features. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9b6c2c113..bce9c1492 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.41' + ext.kotlin_version = '1.2.60' repositories { mavenCentral() diff --git a/common/build.gradle b/kmath-common/build.gradle similarity index 100% rename from common/build.gradle rename to kmath-common/build.gradle diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt similarity index 69% rename from common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt index 71147d56e..6f06f5980 100644 --- a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,5 +1,16 @@ package scientifik.kmath.operations + +/** + * The generic mathematics elements which is able to store its context + */ +interface MathElement{ + /** + * The context this element belongs to + */ + val context: S +} + /** * A general interface representing linear context of some kind. * The context defines sum operation for its elements and multiplication by real value. @@ -37,23 +48,20 @@ interface Space { /** * The element of linear context - * @param S self type of the element. Needed for static type checking + * @param T self type of the element. Needed for static type checking + * @param S the type of space */ -interface SpaceElement> { - /** - * The context this element belongs to - */ - val context: Space +interface SpaceElement>: MathElement { /** * Self value. Needed for static type checking. Needed to avoid type erasure on JVM. */ - val self: S + val self: T - operator fun plus(b: S): S = context.add(self, b) - operator fun minus(b: S): S = context.add(self, context.multiply(b, -1.0)) - operator fun times(k: Number): S = context.multiply(self, k.toDouble()) - operator fun div(k: Number): S = context.multiply(self, 1.0 / k.toDouble()) + operator fun plus(b: T): T = context.add(self, b) + operator fun minus(b: T): T = context.add(self, context.multiply(b, -1.0)) + operator fun times(k: Number): T = context.multiply(self, k.toDouble()) + operator fun div(k: Number): T = context.multiply(self, 1.0 / k.toDouble()) } /** @@ -77,10 +85,10 @@ interface Ring : Space { /** * Ring element */ -interface RingElement> : SpaceElement { - override val context: Ring +interface RingElement> : SpaceElement { + override val context: S - operator fun times(b: S): S = context.multiply(self, b) + operator fun times(b: T): T = context.multiply(self, b) } /** @@ -96,8 +104,8 @@ interface Field : Ring { /** * Field element */ -interface FieldElement> : RingElement { - override val context: Field +interface FieldElement> : RingElement { + override val context: S - operator fun div(b: S): S = context.divide(self, b) + operator fun div(b: T): T = context.divide(self, b) } \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-common/src/main/kotlin/scientifik/kmath/operations/Fields.kt similarity index 65% rename from common/src/main/kotlin/scientifik/kmath/operations/Fields.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/operations/Fields.kt index 7c9e2ddb7..2f3eb99ff 100644 --- a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-common/src/main/kotlin/scientifik/kmath/operations/Fields.kt @@ -1,23 +1,39 @@ package scientifik.kmath.operations +import kotlin.math.pow import kotlin.math.sqrt /** * Field for real values */ -object RealField : Field { +object RealField : Field, TrigonometricOperations, PowerOperations, ExponentialOperations { override val zero: Real = Real(0.0) override fun add(a: Real, b: Real): Real = Real(a.value + b.value) override val one: Real = Real(1.0) override fun multiply(a: Real, b: Real): Real = Real(a.value * b.value) override fun multiply(a: Real, k: Double): Real = Real(a.value * k) override fun divide(a: Real, b: Real): Real = Real(a.value / b.value) + + override fun sin(arg: Real): Real = Real(kotlin.math.sin(arg.value)) + override fun cos(arg: Real): Real = Real(kotlin.math.cos(arg.value)) + + override fun power(arg: Real, pow: Double): Real = Real(arg.value.pow(pow)) + + override fun exp(arg: Real): Real = Real(kotlin.math.exp(arg.value)) + + override fun ln(arg: Real): Real = Real(kotlin.math.ln(arg.value)) } /** - * Real field element wrapping double + * Real field element wrapping double. + * + * TODO could be replaced by inline class in kotlin 1.3 if it would allow to avoid boxing */ -class Real(val value: Double) : FieldElement, Number() { +class Real(val value: Double) : Number(), FieldElement { + /* + * The class uses composition instead of inheritance since Double is final + */ + override fun toByte(): Byte = value.toByte() override fun toChar(): Char = value.toChar() override fun toDouble(): Double = value @@ -29,8 +45,10 @@ class Real(val value: Double) : FieldElement, Number() { //values are dynamically calculated to save memory override val self get() = this + override val context get() = RealField + } /** @@ -54,10 +72,9 @@ object ComplexField : Field { /** * Complex number class */ -data class Complex(val re: Double, val im: Double) : FieldElement { - override val self: Complex - get() = this - override val context: Field +data class Complex(val re: Double, val im: Double) : FieldElement { + override val self: Complex get() = this + override val context: ComplexField get() = ComplexField /** @@ -72,15 +89,15 @@ data class Complex(val re: Double, val im: Double) : FieldElement { val module: Double get() = sqrt(square) - - //TODO is it convenient? - operator fun not() = conjugate } +/** + * A field for double without boxing. Does not produce appropriate field element + */ object DoubleField : Field { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b - override fun multiply(a: Double, b: Double): Double = a * b + override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b override val one: Double = 1.0 override fun divide(a: Double, b: Double): Double = a / b } \ No newline at end of file diff --git a/kmath-common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt new file mode 100644 index 000000000..18ef89407 --- /dev/null +++ b/kmath-common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.operations + + +/* Trigonometric operations */ + +/** + * A container for trigonometric operations for specific type. Trigonometric operations are limited to fields. + * + * The operations are not exposed to class directly to avoid method bloat but instead are declared in the field. + * It also allows to override behavior for optional operations + * + */ +interface TrigonometricOperations: Field { + fun sin(arg: T): T + fun cos(arg: T): T + + fun tg(arg: T): T = sin(arg) / cos(arg) + + fun ctg(arg: T): T = cos(arg) / sin(arg) +} + +fun >> sin(arg: T): T = arg.context.sin(arg) +fun >> cos(arg: T): T = arg.context.cos(arg) +fun >> tg(arg: T): T = arg.context.tg(arg) +fun >> ctg(arg: T): T = arg.context.ctg(arg) + +/* Power and roots */ + +/** + * A context extension to include power operations like square roots, etc + */ +interface PowerOperations { + fun power(arg: T, pow: Double): T +} + +infix fun >> T.pow(power: Double): T = context.power(this, power) +fun >> sqrt(arg: T): T = arg pow 0.5 +fun >> sqr(arg: T): T = arg pow 2.0 + +/* Exponential */ + +interface ExponentialOperations{ + fun exp(arg: T): T + fun ln(arg: T): T +} + +fun >> exp(arg:T): T = arg.context.exp(arg) +fun >> ln(arg:T): T = arg.context.ln(arg) \ No newline at end of file diff --git a/kmath-common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt b/kmath-common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt new file mode 100644 index 000000000..62bad9b75 --- /dev/null +++ b/kmath-common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt @@ -0,0 +1,144 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.SpaceElement + +/** + * The space for linear elements. Supports scalar product alongside with standard linear operations. + * @param T type of individual element of the vector or matrix + * @param V the type of vector space element + */ +abstract class LinearSpace>(val rows: Int, val columns: Int, val field: Field) : Space { + + /** + * Produce the element of this space + */ + abstract fun produce(initializer: (Int, Int) -> T): V + + /** + * Produce new linear space with given dimensions + */ + abstract fun produceSpace(rows: Int, columns: Int): LinearSpace + + override val zero: V by lazy { + produce { _, _ -> field.zero } + } + + override fun add(a: V, b: V): V { + return produce { i, j -> with(field) { a[i, j] + b[i, j] } } + } + + override fun multiply(a: V, k: Double): V { + //TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser + return produce { i, j -> with(field) { a[i, j] * k } } + } + + /** + * Dot product + */ + fun multiply(a: V, b: V): V { + if (a.rows != b.columns) { + //TODO replace by specific exception + error("Dimension mismatch in vector dot product") + } + return produceSpace(a.rows, b.columns).produce { i, j -> + (0..a.columns).asSequence().map { k -> field.multiply(a[i, k], b[k, j]) }.reduce { first, second -> field.add(first, second) } + } + } + + infix fun V.dot(b: V): V = multiply(this, b) +} + +/** + * A matrix-like structure that is not dependent on specific space implementation + */ +interface LinearStructure { + val rows: Int + val columns: Int + + operator fun get(i: Int, j: Int): T + + fun transpose(): LinearStructure { + return object : LinearStructure { + override val rows: Int = this@LinearStructure.columns + override val columns: Int = this@LinearStructure.rows + override fun get(i: Int, j: Int): T = this@LinearStructure.get(j, i) + } + } +} + +interface Vector : LinearStructure { + override val columns: Int + get() = 1 + + operator fun get(i: Int) = get(i, 0) +} + + +/** + * DoubleArray-based implementation of vector space + */ +class ArraySpace(rows: Int, columns: Int, field: Field) : LinearSpace>(rows, columns, field) { + + override fun produce(initializer: (Int, Int) -> T): LinearStructure = ArrayMatrix(this, initializer) + + + override fun produceSpace(rows: Int, columns: Int): LinearSpace> { + return ArraySpace(rows, columns, field) + } +} + +/** + * Member of [ArraySpace] which wraps 2-D array + */ +class ArrayMatrix(override val context: ArraySpace, initializer: (Int, Int) -> T) : LinearStructure, SpaceElement, ArraySpace> { + + val list: List> = (0 until rows).map { i -> (0 until columns).map { j -> initializer(i, j) } } + + override val rows: Int get() = context.rows + + override val columns: Int get() = context.columns + + override fun get(i: Int, j: Int): T { + return list[i][j] + } + + override val self: ArrayMatrix get() = this +} + + +class ArrayVector(override val context: ArraySpace, initializer: (Int) -> T) : Vector, SpaceElement, ArraySpace> { + + init { + if (context.columns != 1) { + error("Vector must have single column") + } + } + + val list: List = (0 until context.rows).map(initializer) + + + override val rows: Int get() = context.rows + + override val columns: Int = 1 + + override fun get(i: Int, j: Int): T { + return list[i] + } + + override val self: ArrayVector get() = this + +} + +fun vector(size: Int, field: Field, initializer: (Int) -> T) = ArrayVector(ArraySpace(size, 1, field), initializer) +//TODO replace by primitive array version +fun realVector(size: Int, initializer: (Int) -> Double) = vector(size, DoubleField, initializer) + +fun Array.asVector(field: Field) = vector(size, field) { this[it] } +//TODO add inferred field from field element +fun DoubleArray.asVector() = realVector(this.size) { this[it] } + +fun matrix(rows: Int, columns: Int, field: Field, initializer: (Int, Int) -> T) = ArrayMatrix(ArraySpace(rows, columns, field), initializer) +fun realMatrix(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = matrix(rows, columns, DoubleField, initializer) \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/kmath-common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt similarity index 58% rename from common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt index 7f27222cc..5acac4ff1 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/kmath-common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt @@ -3,22 +3,32 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement +/** + * An exception is thrown when the expected ans actual shape of NDArray differs + */ class ShapeMismatchException(val expected: List, val actual: List) : RuntimeException() /** * Field for n-dimensional arrays. * @param shape - the list of dimensions of the array * @param field - operations field defined on individual array element + * @param T the type of the element contained in NDArray */ abstract class NDField(val shape: List, val field: Field) : Field> { + /** * Create new instance of NDArray using field shape and given initializer + * The producer takes list of indices as argument and returns contained value */ abstract fun produce(initializer: (List) -> T): NDArray - override val zero: NDArray - get() = produce { this.field.zero } + override val zero: NDArray by lazy { + produce { this.field.zero } + } + /** + * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field + */ private fun checkShape(vararg arrays: NDArray) { arrays.forEach { if (shape != it.shape) { @@ -40,7 +50,7 @@ abstract class NDField(val shape: List, val field: Field) : Field, k: Double): NDArray { checkShape(a) - return produce { with(field) {a[it] * k} } + return produce { with(field) { a[it] * k } } } override val one: NDArray @@ -51,7 +61,7 @@ abstract class NDField(val shape: List, val field: Field) : Field, b: NDArray): NDArray { checkShape(a) - return produce { with(field) {a[it] * b[it]} } + return produce { with(field) { a[it] * b[it] } } } /** @@ -59,18 +69,18 @@ abstract class NDField(val shape: List, val field: Field) : Field, b: NDArray): NDArray { checkShape(a) - return produce { with(field) {a[it] / b[it]} } + return produce { with(field) { a[it] / b[it] } } } } -interface NDArray : FieldElement>, Iterable, T>> { +interface NDArray : FieldElement, NDField> { /** * The list of dimensions of this NDArray */ val shape: List - get() = (context as NDField).shape + get() = context.shape /** * The number of dimentsions for this array @@ -87,14 +97,14 @@ interface NDArray : FieldElement>, Iterable, T>> { return get(*index.toIntArray()) } - override operator fun iterator(): Iterator, T>> { + operator fun iterator(): Iterator, T>> { return iterateIndexes(shape).map { Pair(it, this[it]) }.iterator() } /** * Generate new NDArray, using given transformation for each element */ - fun transform(action: (List, T) -> T): NDArray = (context as NDField).produce { action(it, this[it]) } + fun transform(action: (List, T) -> T): NDArray = context.produce { action(it, this[it]) } companion object { /** @@ -115,6 +125,79 @@ interface NDArray : FieldElement>, Iterable, T>> { } } +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun Function1.invoke(ndArray: NDArray): NDArray = ndArray.transform { _, value -> this(value) } + +/* plus and minus */ + +/** + * Summation operation for [NDArray] and single element + */ +operator fun NDArray.plus(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg + value + } +} + +/** + * Reverse sum operation + */ +operator fun T.plus(arg: NDArray): NDArray = arg + this + +/** + * Subtraction operation between [NDArray] and single element + */ +operator fun NDArray.minus(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg - value + } +} + +/** + * Reverse minus operation + */ +operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field){ + this@minus - value + } +} + +/* prod and div */ + +/** + * Product operation for [NDArray] and single element + */ +operator fun NDArray.times(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg * value + } +} + +/** + * Reverse product operation + */ +operator fun T.times(arg: NDArray): NDArray = arg * this + +/** + * Division operation between [NDArray] and single element + */ +operator fun NDArray.div(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg / value + } +} + +/** + * Reverse division operation + */ +operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field){ + this@div/ value + } +} + /** * Create a platform-specific NDArray of doubles */ diff --git a/kmath-common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/kmath-common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt new file mode 100644 index 000000000..b07158cc3 --- /dev/null +++ b/kmath-common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -0,0 +1,14 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +class RealFieldTest { + @Test + fun testSqrt() { + val sqrt = with(RealField) { + sqrt(25 * one) + } + assertEquals(5.0, sqrt.toDouble()) + } +} \ No newline at end of file diff --git a/jvm/build.gradle b/kmath-jvm/build.gradle similarity index 87% rename from jvm/build.gradle rename to kmath-jvm/build.gradle index 6dc9a0303..82bda0270 100644 --- a/jvm/build.gradle +++ b/kmath-jvm/build.gradle @@ -5,7 +5,7 @@ repositories { } dependencies { - expectedBy project(":common") + expectedBy project(":kmath-common") compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile "junit:junit:4.12" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" diff --git a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt b/kmath-jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt similarity index 92% rename from jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt rename to kmath-jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt index 3be892ced..2c213f91e 100644 --- a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt +++ b/kmath-jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt @@ -35,6 +35,7 @@ private class RealNDField(shape: List) : NDField(shape, DoubleField override fun produce(initializer: (List) -> Double): NDArray { //TODO use sparse arrays for large capacities val buffer = DoubleBuffer.allocate(capacity) + //FIXME there could be performance degradation due to iteration procedure. Replace by straight iteration NDArray.iterateIndexes(shape).forEach { buffer.put(offset(it), initializer(it)) } @@ -68,7 +69,7 @@ private class RealNDField(shape: List) : NDField(shape, DoubleField //TODO generate fixed hash code for quick comparison? - override val self: NDArray = this + override val self: NDArray get() = this } } diff --git a/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt b/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt new file mode 100644 index 000000000..70fd8a6a9 --- /dev/null +++ b/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt @@ -0,0 +1,26 @@ +package scientifik.kmath.structures + +import org.junit.Assert.assertEquals +import org.junit.Test + +class ArrayMatrixTest { + + @Test + fun testSum() { + val vector1 = realVector(5) { it.toDouble() } + val vector2 = realVector(5) { 5 - it.toDouble() } + val sum = vector1 + vector2 + assertEquals(5.0, sum[2, 0], 0.1) + } + + @Test + fun testDot() { + val vector1 = realVector(5) { it.toDouble() } + val vector2 = realVector(5) { 5 - it.toDouble() } + val product = with(vector1.context) { + vector1 dot (vector2.transpose()) + } + + assertEquals(10.0, product[1, 0], 0.1) + } +} \ No newline at end of file diff --git a/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt b/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt similarity index 59% rename from jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt rename to kmath-jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt index 00e1fd7b4..9bed502f9 100644 --- a/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt +++ b/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt @@ -1,6 +1,7 @@ package scientifik.kmath.structures import org.junit.Assert.assertEquals +import kotlin.math.pow import kotlin.test.Test class RealNDFieldTest { @@ -14,8 +15,8 @@ class RealNDFieldTest { } @Test - fun testProduct(){ - val product = array1*array2 + fun testProduct() { + val product = array1 * array2 assertEquals(0.0, product[2, 2], 0.1) } @@ -24,11 +25,18 @@ class RealNDFieldTest { val array = real2DArray(3, 3) { i, j -> (i * 10 + j).toDouble() } - for(i in 0..2){ - for(j in 0..2){ - val expected= (i * 10 + j).toDouble() - assertEquals("Error at index [$i, $j]", expected, array[i,j], 0.1) + for (i in 0..2) { + for (j in 0..2) { + val expected = (i * 10 + j).toDouble() + assertEquals("Error at index [$i, $j]", expected, array[i, j], 0.1) } } } + + @Test + fun testExternalFunction() { + val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 } + val result = function(array1) + 1.0 + assertEquals(10.0, result[1,1],0.01) + } } diff --git a/settings.gradle b/settings.gradle index a60b00349..9dce0ac08 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'kmath' -include 'common' -include 'jvm' +include 'kmath-common' +include 'kmath-jvm'