Initial commit. Algebra operations. NDArray

This commit is contained in:
Alexander Nozik 2018-04-22 15:58:20 +03:00
parent ed6d56b627
commit b42beb4b68
14 changed files with 509 additions and 6 deletions

7
.gitignore vendored
View File

@ -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

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$USER_HOME$/.posh_gvm/gradle/current" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/common" />
<option value="$PROJECT_DIR$/jvm" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_10" project-jdk-name="10" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="Common (experimental) " useProjectSettings="false">
<compilerSettings />
<compilerArguments>
<option name="destination" value="$MODULE_DIR$/../../../common/build/classes/kotlin/main" />
<option name="classpath" value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.2.40/37abadf1cca4450f39672a80d24a379d2fd06356/kotlin-stdlib-common-1.2.40.jar" />
<option name="languageVersion" value="1.2" />
<option name="apiVersion" value="1.2" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array />
</option>
<option name="multiPlatform" value="true" />
</compilerArguments>
</configuration>
</facet>
</component>
</module>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="Common (experimental) " useProjectSettings="false">
<compilerSettings />
<compilerArguments>
<option name="destination" value="$MODULE_DIR$/../../../common/build/classes/kotlin/test" />
<option name="classpath" value="$MODULE_DIR$/../../../common/build/classes/java/main;D:/Work/Projects/kmath/common/build/classes/kotlin/main;D:/Work/Projects/kmath/common/build/resources/main;C:/Users/darksnake/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.2.40/2d4a60c84c93f625f5bffe4cc76b21b243688f5a/kotlin-test-annotations-common-1.2.40.jar;C:/Users/darksnake/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.2.40/2364922e3ca01d51cad8f585a2c8c8d731bb375a/kotlin-test-common-1.2.40.jar;C:/Users/darksnake/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.2.40/37abadf1cca4450f39672a80d24a379d2fd06356/kotlin-stdlib-common-1.2.40.jar;D:/Work/Projects/kmath/common/build/classes/kotlin/main" />
<option name="languageVersion" value="1.2" />
<option name="apiVersion" value="1.2" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array />
</option>
<option name="multiPlatform" value="true" />
</compilerArguments>
</configuration>
</facet>
</component>
</module>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

3
build.gradle Normal file
View File

@ -0,0 +1,3 @@
group = 'scientifik'
version = '0.1-SNAPSHOT'

25
common/build.gradle Normal file
View File

@ -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"
}

View File

@ -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<T> {
/**
* 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<S : SpaceElement<S>> {
/**
* The context this element belongs to
*/
val context: Space<S>
/**
* 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<T> : Space<T> {
/**
* 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<S : RingElement<S>> : SpaceElement<S> {
override val context: Ring<S>
operator fun times(b: S): S = with(context) { self * b }
}
/**
* Four operations algebra
*/
interface Field<T> : Ring<T> {
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<S : FieldElement<S>> : RingElement<S> {
override val context: Field<S>
operator fun div(b: S): S = with(context) { self / b }
}

View File

@ -0,0 +1,78 @@
package scientifik.kmath.operations
import kotlin.math.sqrt
/**
* Field for real values
*/
object RealField : Field<Real> {
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<Real>, 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<Complex> {
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<Complex> {
override val self: Complex
get() = this
override val context: Field<Complex>
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
}

View File

@ -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<Int>, val actual: List<Int>) : 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<T : FieldElement<T>>(val shape: List<Int>, val elementField: Field<T>) : Field<NDArray<T>> {
/**
* Create new instance of NDArray using field shape and given initializer
*/
abstract fun produce(initializer: (List<Int>) -> T): NDArray<T>
override val zero: NDArray<T>
get() = produce { elementField.zero }
private fun checkShape(vararg arrays: NDArray<T>) {
arrays.forEach {
if (shape != it.shape) {
throw ShapeMismatchException(shape, it.shape)
}
}
}
/**
* Element-by-element addition
*/
override fun add(a: NDArray<T>, b: NDArray<T>): NDArray<T> {
checkShape(a, b)
return produce { a[it] + b[it] }
}
/**
* Multiply all elements by cinstant
*/
override fun multiply(a: NDArray<T>, k: Double): NDArray<T> {
checkShape(a)
return produce { a[it] * k }
}
override val one: NDArray<T>
get() = produce { elementField.one }
/**
* Element-by-element multiplication
*/
override fun multiply(a: NDArray<T>, b: NDArray<T>): NDArray<T> {
checkShape(a)
return produce { a[it] * b[it] }
}
/**
* Element-by-element division
*/
override fun divide(a: NDArray<T>, b: NDArray<T>): NDArray<T> {
checkShape(a)
return produce { a[it] / b[it] }
}
}
interface NDArray<T : FieldElement<T>> : FieldElement<NDArray<T>>, Iterable<Pair<List<Int>, T>> {
/**
* The list of dimensions of this NDArray
*/
val shape: List<Int>
get() = (context as NDField<T>).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<Int>): T {
return get(*index.toIntArray())
}
override operator fun iterator(): Iterator<Pair<List<Int>, T>> {
return iterateIndexes(shape).map { Pair(it, this[it]) }.iterator()
}
/**
* Generate new NDArray, using given transformation for each element
*/
fun transform(action: (List<Int>, T) -> T): NDArray<T> = (context as NDField<T>).produce { action(it, this[it]) }
companion object {
/**
* Iterate over all indexes in the nd-shape
*/
fun iterateIndexes(shape: List<Int>): Sequence<List<Int>> {
return if (shape.size == 1) {
(0 until shape[0]).asSequence().map { listOf(it) }
} else {
val tailShape = ArrayList(shape).apply { remove(0) }
val tailSequence: List<List<Int>> = 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<Int>, initializer: (List<Int>) -> Double): NDArray<Real>

33
jvm/build.gradle Normal file
View File

@ -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"

View File

@ -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<Int>) : NDField<Real>(shape, RealField) {
/**
* Strides for memory access
*/
private val strides: List<Int> by lazy {
ArrayList<Int>(shape.size).apply {
var current = 1
shape.forEach{
current *=it
add(current)
}
}
}
fun offset(index: List<Int>): 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<Int>) -> Real): NDArray<Real> {
//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<Real> {
override val context: Field<NDArray<Real>>
get() = this@RealNDField
override fun get(vararg index: Int): Real {
return Real(data.get(offset(index.asList())))
}
override val self: NDArray<Real>
get() = this
}
}
actual fun RealNDArray(shape: List<Int>, initializer: (List<Int>) -> Double): NDArray<Real> {
//TODO cache fields?
return RealNDField(shape).produce { Real(initializer(it)) }
}

4
settings.gradle Normal file
View File

@ -0,0 +1,4 @@
rootProject.name = 'kmath'
include 'common'
include 'jvm'