diff --git a/.gitignore b/.gitignore index a1fc39c07..1e4a58980 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,9 @@ .gradle /build/ - -# Ignore Gradle GUI config -gradle-app.setting +.idea/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar # Cache of project .gradletasknamecache - -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..188aca7b9 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..9870be164 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules/common/common_main.iml b/.idea/modules/common/common_main.iml new file mode 100644 index 000000000..0e9be6f8b --- /dev/null +++ b/.idea/modules/common/common_main.iml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/common/common_test.iml b/.idea/modules/common/common_test.iml new file mode 100644 index 000000000..672412fbd --- /dev/null +++ b/.idea/modules/common/common_test.iml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..fe07b921e --- /dev/null +++ b/build.gradle @@ -0,0 +1,3 @@ +group = 'scientifik' +version = '0.1-SNAPSHOT' + diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 000000000..bae2e09d7 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,25 @@ +buildscript { + ext.kotlin_version = '1.2.40' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +description = "Platform-independent interfaces for kotlin maths" + +apply plugin: 'kotlin-platform-common' + +repositories { + mavenCentral() +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version" +} + diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt b/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt new file mode 100644 index 000000000..4b74cbc7a --- /dev/null +++ b/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt @@ -0,0 +1,103 @@ +package scientifik.kmath.operations + +/** + * A general interface representing linear context of some kind. + * The context defines sum operation for its elements and multiplication by real value. + * One must note that in some cases context is a singleton class, but in some cases it + * works as a context for operations inside it. + * + * TODO do we need commutative context? + */ +interface Space { + /** + * Neutral element for sum operation + */ + val zero: T + + /** + * Addition operation for two context elements + */ + fun add(a: T, b: T): T + + /** + * Multiplication operation for context element and real number + */ + fun multiply(a: T, k: Double): T + + //Operation to be performed in this context + operator fun T.unaryMinus(): T = multiply(this, -1.0) + + operator fun T.plus(b: T): T = add(this, b) + operator fun T.minus(b: T): T = add(this, -b) + operator fun T.times(k: Number) = multiply(this, k.toDouble()) + operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) + operator fun Number.times(b: T) = b * this + +} + +/** + * The element of linear context + * @param S self type of the element. Needed for static type checking + */ +interface SpaceElement> { + /** + * The context this element belongs to + */ + val context: Space + + /** + * Self value. Needed for static type checking. Needed to avoid type erasure on JVM. + */ + val self: S + + operator fun plus(b: S): S = with(context) { self + b } + operator fun minus(b: S): S = with(context) { self - b } + operator fun times(k: Number): S = with(context) { self * k } + operator fun div(k: Number): S = with(context) { self / k } +} + +/** + * The same as {@link Space} but with additional multiplication operation + */ +interface Ring : Space { + /** + * neutral operation for multiplication + */ + val one: T + + /** + * Multiplication for two field elements + */ + fun multiply(a: T, b: T): T + + operator fun T.times(b: T): T = multiply(this, b) + +} + +/** + * Ring element + */ +interface RingElement> : SpaceElement { + override val context: Ring + + operator fun times(b: S): S = with(context) { self * b } +} + +/** + * Four operations algebra + */ +interface Field : Ring { + fun divide(a: T, b: T): T + + operator fun T.div(b: T): T = divide(this, b) + operator fun Double.div(b: T) = this * divide(one, b) +} + +/** + * Field element + */ +interface FieldElement> : RingElement { + override val context: Field + + operator fun div(b: S): S = with(context) { self / b } +} \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt b/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt new file mode 100644 index 000000000..40414879b --- /dev/null +++ b/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt @@ -0,0 +1,78 @@ +package scientifik.kmath.operations + +import kotlin.math.sqrt + +/** + * Field for real values + */ +object RealField : Field { + 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) +} + +/** + * Real field element wrapping double + */ +class Real(val value: Double) : FieldElement, Number() { + override fun toByte(): Byte = value.toByte() + override fun toChar(): Char = value.toChar() + override fun toDouble(): Double = value + override fun toFloat(): Float = value.toFloat() + override fun toInt(): Int = value.toInt() + override fun toLong(): Long = value.toLong() + override fun toShort(): Short = value.toShort() + + //values are dynamically calculated to save memory + override val self + get() = this + override val context + get() = RealField +} + +/** + * A field for complex numbers + */ +object ComplexField : Field { + override val zero: Complex = Complex(0.0, 0.0) + + override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im) + + override fun multiply(a: Complex, k: Double): Complex = Complex(a.re * k, a.im * k) + + override val one: Complex = Complex(1.0, 0.0) + + override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) + + override fun divide(a: Complex, b: Complex): Complex = Complex(a.re * b.re + a.im * b.im, a.re * b.im - a.im * b.re) / b.square + +} + +/** + * Complex number class + */ +data class Complex(val re: Double, val im: Double) : FieldElement { + override val self: Complex + get() = this + override val context: Field + get() = ComplexField + + /** + * A complex conjugate + */ + val conjugate: Complex + get() = Complex(re, -im) + + val square: Double + get() = re * re + im * im + + val module: Double + get() = sqrt(square) + + + //TODO is it convenient? + operator fun not() = conjugate +} \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt new file mode 100644 index 000000000..4ab01da56 --- /dev/null +++ b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt @@ -0,0 +1,120 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement +import scientifik.kmath.operations.Real + +class ShapeMismatchException(val expected: List, val actual: List) : RuntimeException() + +/** + * Field for n-dimensional arrays. + * @param shape - the list of dimensions of the array + * @param elementField - operations field defined on individual array element + */ +abstract class NDField>(val shape: List, val elementField: Field) : Field> { + /** + * Create new instance of NDArray using field shape and given initializer + */ + abstract fun produce(initializer: (List) -> T): NDArray + + override val zero: NDArray + get() = produce { elementField.zero } + + private fun checkShape(vararg arrays: NDArray) { + arrays.forEach { + if (shape != it.shape) { + throw ShapeMismatchException(shape, it.shape) + } + } + } + + /** + * Element-by-element addition + */ + override fun add(a: NDArray, b: NDArray): NDArray { + checkShape(a, b) + return produce { a[it] + b[it] } + } + + /** + * Multiply all elements by cinstant + */ + override fun multiply(a: NDArray, k: Double): NDArray { + checkShape(a) + return produce { a[it] * k } + } + + override val one: NDArray + get() = produce { elementField.one } + + /** + * Element-by-element multiplication + */ + override fun multiply(a: NDArray, b: NDArray): NDArray { + checkShape(a) + return produce { a[it] * b[it] } + } + + /** + * Element-by-element division + */ + override fun divide(a: NDArray, b: NDArray): NDArray { + checkShape(a) + return produce { a[it] / b[it] } + } +} + + +interface NDArray> : FieldElement>, Iterable, T>> { + + /** + * The list of dimensions of this NDArray + */ + val shape: List + get() = (context as NDField).shape + + /** + * The number of dimentsions for this array + */ + val dimension: Int + get() = shape.size + + /** + * Get the element with given indexes. If number of indexes is different from {@link dimension}, throws exception. + */ + operator fun get(vararg index: Int): T + + operator fun get(index: List): T { + return get(*index.toIntArray()) + } + + override 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]) } + + companion object { + /** + * Iterate over all indexes in the nd-shape + */ + fun iterateIndexes(shape: List): Sequence> { + return if (shape.size == 1) { + (0 until shape[0]).asSequence().map { listOf(it) } + } else { + val tailShape = ArrayList(shape).apply { remove(0) } + val tailSequence: List> = iterateIndexes(tailShape).toList() + (0 until shape[0]).asSequence().map { firstIndex -> + //adding first element to each of provided index lists + tailSequence.map { listOf(firstIndex) + it }.asSequence() + }.flatten() + } + } + } +} + + +expect fun RealNDArray(shape: List, initializer: (List) -> Double): NDArray \ No newline at end of file diff --git a/jvm/build.gradle b/jvm/build.gradle new file mode 100644 index 000000000..a8402b75c --- /dev/null +++ b/jvm/build.gradle @@ -0,0 +1,33 @@ +buildscript { + ext.kotlin_version = '1.2.40' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + + +apply plugin: 'kotlin-platform-jvm' + +repositories { + mavenCentral() +} + +dependencies { + expectedBy project(":common") + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testCompile "junit:junit:4.12" + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} +sourceCompatibility = "1.8" \ No newline at end of file diff --git a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt b/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt new file mode 100644 index 000000000..3d00b79d5 --- /dev/null +++ b/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt @@ -0,0 +1,64 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Real +import scientifik.kmath.operations.RealField +import java.nio.DoubleBuffer + +private class RealNDField(shape: List) : NDField(shape, RealField) { + + /** + * Strides for memory access + */ + private val strides: List by lazy { + ArrayList(shape.size).apply { + var current = 1 + shape.forEach{ + current *=it + add(current) + } + } + } + + fun offset(index: List): Int { + return index.mapIndexed { i, value -> + if (value < 0 || value >= shape[i]) { + throw RuntimeException("Index out of shape bounds: ($i,$value)") + } + value * strides[i] + }.sum() + } + + val capacity: Int + get() = strides[shape.size - 1] + + + override fun produce(initializer: (List) -> Real): NDArray { + //TODO use sparse arrays for large capacities + val buffer = DoubleBuffer.allocate(capacity) + NDArray.iterateIndexes(shape).forEach { + buffer.put(offset(it), initializer(it).value) + } + return RealNDArray(buffer) + } + + inner class RealNDArray(val data: DoubleBuffer) : NDArray { + + override val context: Field> + get() = this@RealNDField + + override fun get(vararg index: Int): Real { + return Real(data.get(offset(index.asList()))) + } + + override val self: NDArray + get() = this + } + +} + + +actual fun RealNDArray(shape: List, initializer: (List) -> Double): NDArray { + //TODO cache fields? + return RealNDField(shape).produce { Real(initializer(it)) } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..a60b00349 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'kmath' +include 'common' +include 'jvm' +