From 4871baf0e5f4d45bf3c7d31916d4b84be187b689 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 10 Mar 2023 12:01:08 +0300 Subject: [PATCH 01/10] Add vector product to Euclidean3DSpace --- gradle.properties | 2 +- .../kmath/geometry/Euclidean3DSpace.kt | 47 ++++++++++++++++++- .../kmath/geometry/Euclidean3DSpaceTest.kt | 46 ++++++++++++------ 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/gradle.properties b/gradle.properties index c3f070c2d..048c9c9f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ kotlin.native.ignoreDisabledTargets=true org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.14.2-kotlin-1.8.10 +toolsVersion=0.14.3-kotlin-1.8.20-RC org.gradle.parallel=true diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt index cc641a3f1..f1cf0bad2 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt @@ -78,8 +78,11 @@ public object Euclidean3DSpace : GeometrySpace, ScaleOperations< } } + public fun vector(x: Double, y: Double, z: Double): DoubleVector3D = + Vector3DImpl(x, y, z) + public fun vector(x: Number, y: Number, z: Number): DoubleVector3D = - Vector3DImpl(x.toDouble(), y.toDouble(), z.toDouble()) + vector(x.toDouble(), y.toDouble(), z.toDouble()) override val zero: DoubleVector3D by lazy { vector(0.0, 0.0, 0.0) } @@ -100,6 +103,48 @@ public object Euclidean3DSpace : GeometrySpace, ScaleOperations< override fun DoubleVector3D.dot(other: DoubleVector3D): Double = x * other.x + y * other.y + z * other.z + private fun leviCivita(i: Int, j: Int, k: Int): Int = when { + // even permutation + i == 0 && j == 1 && k == 2 -> 1 + i == 1 && j == 2 && k == 0 -> 1 + i == 2 && j == 0 && k == 1 -> 1 + // odd permutations + i == 2 && j == 1 && k == 0 -> -1 + i == 0 && j == 2 && k == 1 -> -1 + i == 1 && j == 0 && k == 2 -> -1 + + else -> 0 + } + + /** + * Compute vector product of [first] and [second]. The basis assumed to be right-handed if [rightBasis] is true and + * left-handed otherwise + */ + public fun vectorProduct( + first: DoubleVector3D, + second: DoubleVector3D, + rightBasis: Boolean = true, + ): DoubleVector3D { + var x = 0.0 + var y = 0.0 + var z = 0.0 + + for (j in (0..2)) { + for (k in (0..2)) { + x += leviCivita(0, j, k) * first[j] * second[k] + y += leviCivita(1, j, k) * first[j] * second[k] + z += leviCivita(2, j, k) * first[j] * second[k] + } + } + + return vector(x, y, z) * (if (rightBasis) 1 else -1) + } + + /** + * Vector product with right basis + */ + public infix fun DoubleVector3D.cross(other: DoubleVector3D): Vector3D = vectorProduct(this, other) + public val xAxis: DoubleVector3D = vector(1.0, 0.0, 0.0) public val yAxis: DoubleVector3D = vector(0.0, 1.0, 0.0) public val zAxis: DoubleVector3D = vector(0.0, 0.0, 1.0) diff --git a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt index 6d9a169eb..291b0ad47 100644 --- a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt @@ -57,23 +57,39 @@ internal class Euclidean3DSpaceTest { } @Test - fun add() { - with(Euclidean3DSpace) { - assertVectorEquals( - vector(1.0, -2.0, 0.001), - vector(1.0, -2.0, 0.001) + zero - ) - assertVectorEquals( - vector(8.0, -3.0, 3.001), - vector(1.0, 2.0, 3.0) + vector(7.0, -5.0, 0.001) - ) - } + fun add() = with(Euclidean3DSpace) { + assertVectorEquals( + vector(1.0, -2.0, 0.001), + vector(1.0, -2.0, 0.001) + zero + ) + assertVectorEquals( + vector(8.0, -3.0, 3.001), + vector(1.0, 2.0, 3.0) + vector(7.0, -5.0, 0.001) + ) } @Test - fun multiply() { - with(Euclidean3DSpace) { - assertVectorEquals(vector(2.0, -4.0, 0.0), vector(1.0, -2.0, 0.0) * 2) - } + fun multiply() = with(Euclidean3DSpace) { + assertVectorEquals(vector(2.0, -4.0, 0.0), vector(1.0, -2.0, 0.0) * 2) } + + @Test + fun vectorProduct() = with(Euclidean3DSpace) { + assertVectorEquals(zAxis, vectorProduct(xAxis, yAxis)) + assertVectorEquals(zAxis, xAxis cross yAxis) + assertVectorEquals(-zAxis, vectorProduct(yAxis, xAxis)) + assertVectorEquals(zAxis, vectorProduct(yAxis, xAxis, rightBasis = false)) + } + + @Test + fun doubleVectorProduct() = with(Euclidean3DSpace) { + val a = vector(1, 2, -3) + val b = vector(-1, 0, 1) + val c = vector(4, 5, 6) + + val res = a cross (b cross c) + val expected = b * (a dot c) - c * (a dot b) + assertVectorEquals(expected, res) + } + } From a3963ac4f50b7042cf21c51c8b5f97c36877249e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 10 Mar 2023 21:40:14 +0300 Subject: [PATCH 02/10] Refactor series naming and docs --- .../kscience/kmath/series/SeriesAlgebra.kt | 79 +++++++++++-------- .../kscience/kmath/series/seriesExtensions.kt | 32 ++++---- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt index 3cd2212f6..45e392693 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt @@ -6,6 +6,7 @@ import space.kscience.kmath.operations.RingOps import space.kscience.kmath.stat.StatisticalAlgebra import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.BufferView +import space.kscience.kmath.structures.getOrNull import kotlin.math.max import kotlin.math.min @@ -33,8 +34,6 @@ public interface Series : Buffer { public val position: Int } -public val Series.absoluteIndices: IntRange get() = position until position + size - /** * A [BufferView] with index offset (both positive and negative) and possible size change */ @@ -60,54 +59,68 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( private val labelResolver: (Int) -> L, ) : RingOps>, StatisticalAlgebra { - public val Buffer.indices: IntRange + /** + * A range of valid offset indices. In general, does not start with zero. + */ + public val Buffer.offsetIndices: IntRange get() = if (this is Series) { - absoluteIndices + position until position + size } else { 0 until size } /** - * Get the value by absolute index in the series algebra or return null if index is out of range + * Get the value by absolute offset in the series algebra or return null if index is out of range */ - public fun Buffer.getAbsoluteOrNull(index: Int): T? = when { - index !in indices -> null - this is Series -> origin[index - position] - else -> get(index) + public fun Buffer.getByOffsetOrNull(index: Int): T? = when { + index !in offsetIndices -> null + this is Series -> origin.getOrNull(index - position) + else -> getOrNull(index) } /** * Get the value by absolute index in the series algebra or throw [IndexOutOfBoundsException] if index is out of range */ - public fun Buffer.getAbsolute(index: Int): T = - getAbsoluteOrNull(index) ?: throw IndexOutOfBoundsException("Index $index is not in $indices") + public fun Buffer.getByOffset(index: Int): T = + getByOffsetOrNull(index) ?: throw IndexOutOfBoundsException("Index $index is not in $offsetIndices") /** - * Create an offset series with index starting point at [index] + * Zero-copy move [Buffer] or [Series] to given [position] ignoring series offset if it is present. */ - public fun Buffer.moveTo(index: Int): Series = if (this is Series) { - SeriesImpl(origin, index, size) + public fun Buffer.moveTo(position: Int): Series = if (this is Series) { + SeriesImpl(origin, position, size) } else { - SeriesImpl(this, index, size) + SeriesImpl(this, position, size) } - public val Buffer.offset: Int get() = if (this is Series) position else 0 + /** + * Zero-copy move [Buffer] or [Series] by given [offset]. If it is [Series], sum intrinsic series position and the [offset]. + */ + public fun Buffer.moveBy(offset: Int): Series = if (this is Series) { + SeriesImpl(origin, position + offset, size) + } else { + SeriesImpl(this, offset, size) + } /** - * Build a new series + * An offset of the buffer start relative to [SeriesAlgebra] zero offset */ - public fun series(size: Int, fromIndex: Int = 0, block: A.(label: L) -> T): Series { + public val Buffer.startOffset: Int get() = if (this is Series) position else 0 + + /** + * Build a new series positioned at [startOffset]. + */ + public fun series(size: Int, startOffset: Int = 0, block: A.(label: L) -> T): Series { return elementAlgebra.bufferFactory(size) { - val index = it + fromIndex + val index = it + startOffset elementAlgebra.block(labelResolver(index)) - }.moveTo(fromIndex) + }.moveTo(startOffset) } /** * Get a label buffer for given buffer. */ - public val Buffer.labels: List get() = indices.map(labelResolver) - + public val Buffer.labels: List get() = offsetIndices.map(labelResolver) /** * Try to resolve element by label and return null if element with a given label is not found @@ -115,7 +128,7 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( public operator fun Buffer.get(label: L): T? { val index = labels.indexOf(label) if (index == -1) return null - return getAbsolute(index + offset) + return getByOffset(index + startOffset) } /** @@ -123,9 +136,9 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( */ public inline fun Buffer.map(crossinline transform: A.(T) -> T): Series { val buf = elementAlgebra.bufferFactory(size) { - elementAlgebra.transform(getAbsolute(it)) + elementAlgebra.transform(getByOffset(it)) } - return buf.moveTo(indices.first) + return buf.moveTo(offsetIndices.first) } /** @@ -134,22 +147,22 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( public inline fun Buffer.mapWithLabel(crossinline transform: A.(arg: T, label: L) -> T): Series { val labels = labels val buf = elementAlgebra.bufferFactory(size) { - elementAlgebra.transform(getAbsolute(it), labels[it]) + elementAlgebra.transform(getByOffset(it), labels[it]) } - return buf.moveTo(indices.first) + return buf.moveTo(offsetIndices.first) } public inline fun Buffer.fold(initial: R, operation: A.(acc: R, T) -> R): R { var accumulator = initial - for (index in this.indices) accumulator = elementAlgebra.operation(accumulator, getAbsolute(index)) + for (index in this.offsetIndices) accumulator = elementAlgebra.operation(accumulator, getByOffset(index)) return accumulator } public inline fun Buffer.foldWithLabel(initial: R, operation: A.(acc: R, arg: T, label: L) -> R): R { val labels = labels var accumulator = initial - for (index in this.indices) accumulator = - elementAlgebra.operation(accumulator, getAbsolute(index), labels[index]) + for (index in this.offsetIndices) accumulator = + elementAlgebra.operation(accumulator, getByOffset(index), labels[index]) return accumulator } @@ -160,11 +173,11 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( other: Buffer, crossinline operation: A.(left: T, right: T) -> T, ): Series { - val newRange = indices.intersect(other.indices) + val newRange = offsetIndices.intersect(other.offsetIndices) return elementAlgebra.bufferFactory(newRange.size) { elementAlgebra.operation( - getAbsolute(it), - other.getAbsolute(it) + getByOffset(it), + other.getByOffset(it) ) }.moveTo(newRange.first) } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/seriesExtensions.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/seriesExtensions.kt index 882fa2c46..fa5e0addd 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/seriesExtensions.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/seriesExtensions.kt @@ -12,32 +12,32 @@ import space.kscience.kmath.structures.Buffer public fun SeriesAlgebra.sin( arg: Buffer, ): Series where BA : BufferAlgebra, BA : TrigonometricOperations> = - bufferAlgebra.sin(arg).moveTo(arg.offset) + bufferAlgebra.sin(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.cos( arg: Buffer, ): Series where BA : BufferAlgebra, BA : TrigonometricOperations> = - bufferAlgebra.cos(arg).moveTo(arg.offset) + bufferAlgebra.cos(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.tan( arg: Buffer, ): Series where BA : BufferAlgebra, BA : TrigonometricOperations> = - bufferAlgebra.tan(arg).moveTo(arg.offset) + bufferAlgebra.tan(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.asin( arg: Buffer, ): Series where BA : BufferAlgebra, BA : TrigonometricOperations> = - bufferAlgebra.asin(arg).moveTo(arg.offset) + bufferAlgebra.asin(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.acos( arg: Buffer, ): Series where BA : BufferAlgebra, BA : TrigonometricOperations> = - bufferAlgebra.acos(arg).moveTo(arg.offset) + bufferAlgebra.acos(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.atan( arg: Buffer, ): Series where BA : BufferAlgebra, BA : TrigonometricOperations> = - bufferAlgebra.atan(arg).moveTo(arg.offset) + bufferAlgebra.atan(arg).moveTo(arg.startOffset) //exponential @@ -45,42 +45,42 @@ public fun SeriesAlgebra.atan( public fun SeriesAlgebra.exp( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.exp(arg).moveTo(arg.offset) + bufferAlgebra.exp(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.ln( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.ln(arg).moveTo(arg.offset) + bufferAlgebra.ln(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.sinh( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.sinh(arg).moveTo(arg.offset) + bufferAlgebra.sinh(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.cosh( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.cosh(arg).moveTo(arg.offset) + bufferAlgebra.cosh(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.tanh( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.tanh(arg).moveTo(arg.offset) + bufferAlgebra.tanh(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.asinh( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.asinh(arg).moveTo(arg.offset) + bufferAlgebra.asinh(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.acosh( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.acosh(arg).moveTo(arg.offset) + bufferAlgebra.acosh(arg).moveTo(arg.startOffset) public fun SeriesAlgebra.atanh( arg: Buffer, ): Series where BA : BufferAlgebra, BA : ExponentialOperations> = - bufferAlgebra.atanh(arg).moveTo(arg.offset) + bufferAlgebra.atanh(arg).moveTo(arg.startOffset) //power @@ -89,9 +89,9 @@ public fun SeriesAlgebra.power( arg: Buffer, pow: Number, ): Series where BA : BufferAlgebra, BA : PowerOperations> = - bufferAlgebra.power(arg, pow).moveTo(arg.offset) + bufferAlgebra.power(arg, pow).moveTo(arg.startOffset) public fun SeriesAlgebra.sqrt( arg: Buffer, ): Series where BA : BufferAlgebra, BA : PowerOperations> = - bufferAlgebra.sqrt(arg).moveTo(arg.offset) \ No newline at end of file + bufferAlgebra.sqrt(arg).moveTo(arg.startOffset) \ No newline at end of file From 72c70302974a1cc568f32a6c6242d7180c77a9fd Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 10 Mar 2023 22:50:41 +0300 Subject: [PATCH 03/10] Add time series example stub --- examples/build.gradle.kts | 9 ++-- .../kscience/kmath/stat/DateTimeSeries.kt | 19 ++++++++ .../kmath/series/MonotonicSeriesAlgebra.kt | 47 +++++++++++++++++++ .../kscience/kmath/series/SeriesAlgebra.kt | 16 +++++-- 4 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 examples/src/main/kotlin/space/kscience/kmath/stat/DateTimeSeries.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/MonotonicSeriesAlgebra.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index da645f012..f7b7794f3 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile + plugins { kotlin("jvm") } @@ -33,6 +35,8 @@ dependencies { implementation(project(":kmath-multik")) implementation("org.jetbrains.kotlinx:multik-default:$multikVersion") + //datetime + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") implementation("org.nd4j:nd4j-native:1.0.0-beta7") @@ -46,9 +50,6 @@ dependencies { // } else implementation("org.nd4j:nd4j-native-platform:1.0.0-beta7") - // multik implementation - implementation("org.jetbrains.kotlinx:multik-default:0.1.0") - implementation("org.slf4j:slf4j-simple:1.7.32") // plotting implementation("space.kscience:plotlykt-server:0.5.0") @@ -62,7 +63,7 @@ kotlin.sourceSets.all { } } -tasks.withType { +tasks.withType { kotlinOptions { jvmTarget = "11" freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" diff --git a/examples/src/main/kotlin/space/kscience/kmath/stat/DateTimeSeries.kt b/examples/src/main/kotlin/space/kscience/kmath/stat/DateTimeSeries.kt new file mode 100644 index 000000000..9836db6ea --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/stat/DateTimeSeries.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.stat + +import kotlinx.datetime.Instant +import space.kscience.kmath.operations.algebra +import space.kscience.kmath.operations.bufferAlgebra +import space.kscience.kmath.series.MonotonicSeriesAlgebra +import space.kscience.kmath.series.SeriesAlgebra +import kotlin.time.Duration + +fun SeriesAlgebra.Companion.time(zero: Instant, step: Duration) = MonotonicSeriesAlgebra( + bufferAlgebra = Double.algebra.bufferAlgebra, + offsetToLabel = { zero + step * it }, + labelToOffset = { (it - zero) / step } +) \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/MonotonicSeriesAlgebra.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/MonotonicSeriesAlgebra.kt new file mode 100644 index 000000000..2bc363934 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/MonotonicSeriesAlgebra.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.series + +import space.kscience.kmath.operations.BufferAlgebra +import space.kscience.kmath.operations.Ring +import space.kscience.kmath.structures.Buffer +import kotlin.math.ceil +import kotlin.math.floor + +/** + * A [SeriesAlgebra] with reverse label to index transformation. + * + * @param [labelToOffset] returns floating point number that is used for index resolution. + */ +public class MonotonicSeriesAlgebra, out BA : BufferAlgebra, L : Comparable>( + bufferAlgebra: BA, + offsetToLabel: (Int) -> L, + private val labelToOffset: (L) -> Double, +) : SeriesAlgebra(bufferAlgebra, offsetToLabel) { + + public val Buffer.labelRange: ClosedRange get() = offsetToLabel(startOffset)..offsetToLabel(startOffset + size) + + /** + * An offset of the given [label] rounded down + */ + public fun floorOffset(label: L): Int = floor(labelToOffset(label)).toInt() + + /** + * An offset of the given [label] rounded up + */ + public fun ceilOffset(label: L): Int = ceil(labelToOffset(label)).toInt() + + /** + * Get value by label (rounded down) or return null if the value is outside series boundaries. + */ + public fun Buffer.getByLabelOrNull(label: L): T? = getByOffsetOrNull(floorOffset(label)) + + /** + * Get value by label (rounded down) or throw [IndexOutOfBoundsException] if the value is outside series boundaries. + */ + public fun Buffer.getByLabel(label: L): T = getByLabelOrNull(label) + ?: throw IndexOutOfBoundsException("Label $label is not in $labelRange") +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt index 45e392693..1847e33b6 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt @@ -24,7 +24,9 @@ internal operator fun IntRange.contains(other: IntRange): Boolean = (other.first //TODO add permutation sort //TODO check rank statistics - +/** + * A [Buffer] with an offset relative to the [SeriesAlgebra] zero. + */ public interface Series : Buffer { public val origin: Buffer @@ -54,9 +56,9 @@ private class SeriesImpl( /** * A scope to operation on series */ -public class SeriesAlgebra, out BA : BufferAlgebra, L>( +public open class SeriesAlgebra, out BA : BufferAlgebra, L>( override val bufferAlgebra: BA, - private val labelResolver: (Int) -> L, + public val offsetToLabel: (Int) -> L, ) : RingOps>, StatisticalAlgebra { /** @@ -107,20 +109,22 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( */ public val Buffer.startOffset: Int get() = if (this is Series) position else 0 + public val Buffer.startLabel: L get() = offsetToLabel(startOffset) + /** * Build a new series positioned at [startOffset]. */ public fun series(size: Int, startOffset: Int = 0, block: A.(label: L) -> T): Series { return elementAlgebra.bufferFactory(size) { val index = it + startOffset - elementAlgebra.block(labelResolver(index)) + elementAlgebra.block(offsetToLabel(index)) }.moveTo(startOffset) } /** * Get a label buffer for given buffer. */ - public val Buffer.labels: List get() = offsetIndices.map(labelResolver) + public val Buffer.labels: List get() = offsetIndices.map(offsetToLabel) /** * Try to resolve element by label and return null if element with a given label is not found @@ -187,6 +191,8 @@ public class SeriesAlgebra, out BA : BufferAlgebra, L>( override fun add(left: Buffer, right: Buffer): Series = left.zip(right) { l, r -> l + r } override fun multiply(left: Buffer, right: Buffer): Buffer = left.zip(right) { l, r -> l * r } + + public companion object } public fun , BA : BufferAlgebra, L> BA.seriesAlgebra(labels: Iterable): SeriesAlgebra { From 28b85b0f53ba5d6e5e82777ffa8d57a770860baa Mon Sep 17 00:00:00 2001 From: Gleb Minaev <43728100+lounres@users.noreply.github.com> Date: Tue, 14 Mar 2023 20:13:34 +0300 Subject: [PATCH 04/10] Remove the choice of left-handed product. Refactor `vectorProduct`. Remove `leviChivita` function. --- .../kmath/geometry/Euclidean3DSpace.kt | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt index f1cf0bad2..38c252bc0 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt @@ -103,45 +103,21 @@ public object Euclidean3DSpace : GeometrySpace, ScaleOperations< override fun DoubleVector3D.dot(other: DoubleVector3D): Double = x * other.x + y * other.y + z * other.z - private fun leviCivita(i: Int, j: Int, k: Int): Int = when { - // even permutation - i == 0 && j == 1 && k == 2 -> 1 - i == 1 && j == 2 && k == 0 -> 1 - i == 2 && j == 0 && k == 1 -> 1 - // odd permutations - i == 2 && j == 1 && k == 0 -> -1 - i == 0 && j == 2 && k == 1 -> -1 - i == 1 && j == 0 && k == 2 -> -1 - - else -> 0 - } - /** - * Compute vector product of [first] and [second]. The basis assumed to be right-handed if [rightBasis] is true and - * left-handed otherwise + * Compute vector product of [first] and [second]. The basis assumed to be right-handed. */ public fun vectorProduct( first: DoubleVector3D, second: DoubleVector3D, - rightBasis: Boolean = true, ): DoubleVector3D { - var x = 0.0 - var y = 0.0 - var z = 0.0 + val (x1, y1, z1) = first + val (x2, y2, z2) = second - for (j in (0..2)) { - for (k in (0..2)) { - x += leviCivita(0, j, k) * first[j] * second[k] - y += leviCivita(1, j, k) * first[j] * second[k] - z += leviCivita(2, j, k) * first[j] * second[k] - } - } - - return vector(x, y, z) * (if (rightBasis) 1 else -1) + return vector(y1 * z2 - y2 * z2, z1 * x2 - z2 * x2, x1 * y2 - x2 * y2) } /** - * Vector product with right basis + * Vector product with a right basis */ public infix fun DoubleVector3D.cross(other: DoubleVector3D): Vector3D = vectorProduct(this, other) From cd2ade881add23d4ac9b3f4b4d5ea594b6ad6d63 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 16 Mar 2023 09:33:17 +0300 Subject: [PATCH 05/10] Revert "Remove the choice of left-handed product. Refactor `vectorProduct`. Remove `leviChivita` function." This reverts commit 28b85b0f53ba5d6e5e82777ffa8d57a770860baa. --- .../kmath/geometry/Euclidean3DSpace.kt | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt index 38c252bc0..f1cf0bad2 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt @@ -103,21 +103,45 @@ public object Euclidean3DSpace : GeometrySpace, ScaleOperations< override fun DoubleVector3D.dot(other: DoubleVector3D): Double = x * other.x + y * other.y + z * other.z + private fun leviCivita(i: Int, j: Int, k: Int): Int = when { + // even permutation + i == 0 && j == 1 && k == 2 -> 1 + i == 1 && j == 2 && k == 0 -> 1 + i == 2 && j == 0 && k == 1 -> 1 + // odd permutations + i == 2 && j == 1 && k == 0 -> -1 + i == 0 && j == 2 && k == 1 -> -1 + i == 1 && j == 0 && k == 2 -> -1 + + else -> 0 + } + /** - * Compute vector product of [first] and [second]. The basis assumed to be right-handed. + * Compute vector product of [first] and [second]. The basis assumed to be right-handed if [rightBasis] is true and + * left-handed otherwise */ public fun vectorProduct( first: DoubleVector3D, second: DoubleVector3D, + rightBasis: Boolean = true, ): DoubleVector3D { - val (x1, y1, z1) = first - val (x2, y2, z2) = second + var x = 0.0 + var y = 0.0 + var z = 0.0 - return vector(y1 * z2 - y2 * z2, z1 * x2 - z2 * x2, x1 * y2 - x2 * y2) + for (j in (0..2)) { + for (k in (0..2)) { + x += leviCivita(0, j, k) * first[j] * second[k] + y += leviCivita(1, j, k) * first[j] * second[k] + z += leviCivita(2, j, k) * first[j] * second[k] + } + } + + return vector(x, y, z) * (if (rightBasis) 1 else -1) } /** - * Vector product with a right basis + * Vector product with right basis */ public infix fun DoubleVector3D.cross(other: DoubleVector3D): Vector3D = vectorProduct(this, other) From ef336af87ddb6df9acc8029e9633234ae5fe6851 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 16 Mar 2023 09:37:03 +0300 Subject: [PATCH 06/10] Fix vector product --- .gitignore | 3 ++- CHANGELOG.md | 1 + examples/build.gradle.kts | 14 +++++----- gradle.properties | 2 +- .../space/kscience/kmath/nd/BufferND.kt | 26 +++++++++++++++++++ .../integration/GaussIntegratorRuleFactory.kt | 2 -- .../kmath/geometry/Euclidean3DSpace.kt | 6 ++--- .../kmath/geometry/Euclidean3DSpaceTest.kt | 1 - 8 files changed, 40 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 34ddf3fd9..7713a9f96 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ out/ !/.idea/copyright/ !/.idea/scopes/ -/kotlin-js-store/yarn.lock +/gradle/yarn.lock + diff --git a/CHANGELOG.md b/CHANGELOG.md index 404366a03..c5fa3f372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] ### Added +- Generic builders for `BufferND` and `MutableBufferND` - `NamedMatrix` - matrix with symbol-based indexing - `Expression` with default arguments - Type-aliases for numbers like `Float64` diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index f7b7794f3..50708eaa9 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -55,17 +55,19 @@ dependencies { implementation("space.kscience:plotlykt-server:0.5.0") } -kotlin.sourceSets.all { - with(languageSettings) { - optIn("kotlin.contracts.ExperimentalContracts") - optIn("kotlin.ExperimentalUnsignedTypes") - optIn("space.kscience.kmath.misc.UnstableKMathAPI") +kotlin { + jvmToolchain(11) + sourceSets.all { + with(languageSettings) { + optIn("kotlin.contracts.ExperimentalContracts") + optIn("kotlin.ExperimentalUnsignedTypes") + optIn("space.kscience.kmath.misc.UnstableKMathAPI") + } } } tasks.withType { kotlinOptions { - jvmTarget = "11" freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" } } diff --git a/gradle.properties b/gradle.properties index 048c9c9f3..cded5934c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ kotlin.native.ignoreDisabledTargets=true org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.14.3-kotlin-1.8.20-RC +toolsVersion=0.14.4-kotlin-1.8.20-RC org.gradle.parallel=true diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt index 55e8bbcf8..a6bab8be1 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt @@ -7,7 +7,9 @@ package space.kscience.kmath.nd import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.BufferFactory import space.kscience.kmath.structures.MutableBuffer +import space.kscience.kmath.structures.MutableBufferFactory /** * Represents [StructureND] over [Buffer]. @@ -29,6 +31,18 @@ public open class BufferND( override fun toString(): String = StructureND.toString(this) } +/** + * Create a generic [BufferND] using provided [initializer] + */ +public fun BufferND( + shape: ShapeND, + bufferFactory: BufferFactory = BufferFactory.boxing(), + initializer: (IntArray) -> T, +): BufferND { + val strides = Strides(shape) + return BufferND(strides, bufferFactory(strides.linearSize) { initializer(strides.index(it)) }) +} + ///** // * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferND] // */ @@ -67,6 +81,18 @@ public open class MutableBufferND( } } +/** + * Create a generic [BufferND] using provided [initializer] + */ +public fun MutableBufferND( + shape: ShapeND, + bufferFactory: MutableBufferFactory = MutableBufferFactory.boxing(), + initializer: (IntArray) -> T, +): MutableBufferND { + val strides = Strides(shape) + return MutableBufferND(strides, bufferFactory(strides.linearSize) { initializer(strides.index(it)) }) +} + ///** // * Transform structure to a new structure using provided [MutableBufferFactory] and optimizing if argument is [MutableBufferND] // */ diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt index fc76ea819..4ed4965c9 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt @@ -9,7 +9,6 @@ import space.kscience.kmath.operations.mapToBuffer import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.asBuffer -import kotlin.jvm.Synchronized import kotlin.math.ulp import kotlin.native.concurrent.ThreadLocal @@ -57,7 +56,6 @@ public object GaussLegendreRuleFactory : GaussIntegratorRuleFactory { private val cache = HashMap, Buffer>>() - @Synchronized private fun getOrBuildRule(numPoints: Int): Pair, Buffer> = cache.getOrPut(numPoints) { buildRule(numPoints) } diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt index f1cf0bad2..3059cefe6 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt @@ -117,13 +117,11 @@ public object Euclidean3DSpace : GeometrySpace, ScaleOperations< } /** - * Compute vector product of [first] and [second]. The basis assumed to be right-handed if [rightBasis] is true and - * left-handed otherwise + * Compute vector product of [first] and [second]. The basis assumed to be right-handed. */ public fun vectorProduct( first: DoubleVector3D, second: DoubleVector3D, - rightBasis: Boolean = true, ): DoubleVector3D { var x = 0.0 var y = 0.0 @@ -137,7 +135,7 @@ public object Euclidean3DSpace : GeometrySpace, ScaleOperations< } } - return vector(x, y, z) * (if (rightBasis) 1 else -1) + return vector(x, y, z) } /** diff --git a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt index 291b0ad47..20e112ad1 100644 --- a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/Euclidean3DSpaceTest.kt @@ -78,7 +78,6 @@ internal class Euclidean3DSpaceTest { assertVectorEquals(zAxis, vectorProduct(xAxis, yAxis)) assertVectorEquals(zAxis, xAxis cross yAxis) assertVectorEquals(-zAxis, vectorProduct(yAxis, xAxis)) - assertVectorEquals(zAxis, vectorProduct(yAxis, xAxis, rightBasis = false)) } @Test From c36af3515e08adb3595a95a6470309eb149fc0c9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 19 Mar 2023 18:39:27 +0300 Subject: [PATCH 07/10] Update trajectory description --- kmath-trajectory/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kmath-trajectory/build.gradle.kts b/kmath-trajectory/build.gradle.kts index 689c3265b..32b87bb06 100644 --- a/kmath-trajectory/build.gradle.kts +++ b/kmath-trajectory/build.gradle.kts @@ -15,7 +15,7 @@ kscience{ } readme { - description = "Path and trajectory optimization" - maturity = space.kscience.gradle.Maturity.PROTOTYPE + description = "Path and trajectory optimization (to be moved to a separate project)" + maturity = space.kscience.gradle.Maturity.DEPRECATED propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) } From 62c8610a9ebb45d5c450ef283ce21ec5e0d4de3c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 19 Mar 2023 19:16:46 +0300 Subject: [PATCH 08/10] Update publishing CD --- .github/workflows/publish.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 794881b09..8087195c2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v3.0.0 - - uses: actions/setup-java@v3.0.0 + - uses: actions/setup-java@v3.10.0 with: java-version: 11 distribution: liberica @@ -26,26 +26,24 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} restore-keys: | ${{ runner.os }}-gradle- - - uses: gradle/wrapper-validation-action@v1.0.4 - name: Publish Windows Artifacts if: matrix.os == 'windows-latest' - uses: gradle/gradle-build-action@v2.1.5 + uses: gradle/gradle-build-action@v2.4.0 with: arguments: | - releaseAll + publishAllPublicationsToSpaceRepository -Ppublishing.enabled=true - -Ppublishing.sonatype=false -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} - name: Publish Mac Artifacts if: matrix.os == 'macOS-latest' - uses: gradle/gradle-build-action@v2.1.5 + uses: gradle/gradle-build-action@v2.4.0 with: arguments: | - releaseMacosX64 - releaseIosArm64 - releaseIosX64 + publishMacosX64PublicationsToSpaceRepository + publishIosX64PublicationsToSpaceRepository + publishIosArm64PublicationsToSpaceRepository + publishIosSimulatorArm64PublicationsToSpaceRepository -Ppublishing.enabled=true - -Ppublishing.sonatype=false -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} From c442eb7e94ff562688b171cfcdf7deb829b71d47 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 19 Mar 2023 19:41:31 +0300 Subject: [PATCH 09/10] Fix publish task names --- .github/workflows/publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8087195c2..0ac8805e0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -40,10 +40,10 @@ jobs: uses: gradle/gradle-build-action@v2.4.0 with: arguments: | - publishMacosX64PublicationsToSpaceRepository - publishIosX64PublicationsToSpaceRepository - publishIosArm64PublicationsToSpaceRepository - publishIosSimulatorArm64PublicationsToSpaceRepository + publishMacosX64PublicationToSpaceRepository + publishIosX64PublicationToSpaceRepository + publishIosArm64PublicationToSpaceRepository + publishIosSimulatorArm64PublicationToSpaceRepository -Ppublishing.enabled=true -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} From 56bba749c026863a02ed6eb461d85d29d56e81e6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 22 Mar 2023 10:54:24 +0300 Subject: [PATCH 10/10] Update publishing --- .github/workflows/publish.yml | 4 ++-- gradle.properties | 2 +- .../kotlin/space/kscience/kmath/coroutines/coroutinesExtra.kt | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0ac8805e0..471388364 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: with: arguments: | publishAllPublicationsToSpaceRepository - -Ppublishing.enabled=true + -Ppublishing.targets=all -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} - name: Publish Mac Artifacts @@ -44,6 +44,6 @@ jobs: publishIosX64PublicationToSpaceRepository publishIosArm64PublicationToSpaceRepository publishIosSimulatorArm64PublicationToSpaceRepository - -Ppublishing.enabled=true + -Ppublishing.targets=all -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} diff --git a/gradle.properties b/gradle.properties index cded5934c..262bcabfb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ kotlin.native.ignoreDisabledTargets=true org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.14.4-kotlin-1.8.20-RC +toolsVersion=0.14.5-kotlin-1.8.20-RC org.gradle.parallel=true diff --git a/kmath-coroutines/src/commonMain/kotlin/space/kscience/kmath/coroutines/coroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/space/kscience/kmath/coroutines/coroutinesExtra.kt index 2fd4f0057..48be93b87 100644 --- a/kmath-coroutines/src/commonMain/kotlin/space/kscience/kmath/coroutines/coroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/space/kscience/kmath/coroutines/coroutinesExtra.kt @@ -8,6 +8,7 @@ package space.kscience.kmath.coroutines import kotlinx.coroutines.* +import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.produce import kotlinx.coroutines.flow.* @@ -57,7 +58,7 @@ public suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCol coroutineScope { //Starting up to N deferred coroutines ahead of time - val channel = produce(capacity = concurrency - 1) { + val channel: ReceiveChannel> = produce(capacity = concurrency - 1) { deferredFlow.collect { value -> value.start(this@coroutineScope) send(value)