diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 794881b09..ab9243f15 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,25 @@ 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 - -Ppublishing.enabled=true - -Ppublishing.sonatype=false + publishAllPublicationsToSpaceRepository + -Ppublishing.targets=all -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 - -Ppublishing.enabled=true - -Ppublishing.sonatype=false + publishMacosX64PublicationToSpaceRepository + publishMacosArm64PublicationToSpaceRepository + publishIosX64PublicationToSpaceRepository + publishIosArm64PublicationToSpaceRepository + publishIosSimulatorArm64PublicationToSpaceRepository + -Ppublishing.targets=all -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} 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..24b592430 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` @@ -23,6 +24,7 @@ ### Deprecated ### Removed +- Trajectory moved to https://github.com/SciProgCentre/maps-kt - Polynomials moved to https://github.com/SciProgCentre/kmath-polynomial ### Fixed diff --git a/build.gradle.kts b/build.gradle.kts index 9b1101a22..cd8dfb4a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ allprojects { } group = "space.kscience" - version = "0.3.1-dev-10" + version = "0.3.1-dev-11" } subprojects { diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index da645f012..50708eaa9 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,25 +50,24 @@ 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") } -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 { +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/gradle.properties b/gradle.properties index c3f070c2d..e33106c0c 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.6-kotlin-1.8.20 org.gradle.parallel=true diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt index a752a8339..bdda674dc 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt @@ -9,7 +9,7 @@ import kotlin.jvm.JvmInline import kotlin.reflect.KClass /** - * A entity that contains a set of features defined by their types + * An entity that contains a set of features defined by their types */ public interface Featured { public fun getFeature(type: FeatureKey): T? diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/collections.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/collections.kt new file mode 100644 index 000000000..90cc5bbfa --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/collections.kt @@ -0,0 +1,22 @@ +/* + * 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.misc + +/** + * The same as [zipWithNext], but includes link between last and first element + */ +public inline fun List.zipWithNextCircular(transform: (a: T, b: T) -> R): List { + if (isEmpty()) return emptyList() + return indices.map { i -> + if (i == size - 1) { + transform(last(), first()) + } else { + transform(get(i), get(i + 1)) + } + } +} + +public inline fun List.zipWithNextCircular(): List> = zipWithNextCircular { l, r -> l to r } \ No newline at end of file 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-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) 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 cc641a3f1..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 @@ -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,46 @@ 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. + */ + public fun vectorProduct( + first: DoubleVector3D, + second: DoubleVector3D, + ): 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) + } + + /** + * 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/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt index ab322ddca..a7f6ae35d 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Line.kt @@ -5,14 +5,23 @@ package space.kscience.kmath.geometry +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * A line formed by [base] vector of start and a [direction] vector. Direction vector is not necessarily normalized, + * A line formed by [start] vector of start and a [direction] vector. Direction vector is not necessarily normalized, * but its length does not affect line properties */ +public interface Line { + public val start: V + public val direction: V +} + @Serializable -public data class Line(val base: V, val direction: V) +@SerialName("Line") +private data class LineImpl(override val start: V, override val direction: V): Line + +public fun Line(base: V, direction: V): Line = LineImpl(base, direction) public typealias Line2D = Line public typealias Line3D = Line @@ -20,8 +29,19 @@ public typealias Line3D = Line /** * A directed line segment between [begin] and [end] */ +public interface LineSegment { + public val begin: V + public val end: V +} + +/** + * Basic implementation for [LineSegment] + */ @Serializable -public data class LineSegment(val begin: V, val end: V) +@SerialName("LineSegment") +private data class LineSegmentImpl(override val begin: V, override val end: V) : LineSegment + +public fun LineSegment(begin: V, end: V): LineSegment = LineSegmentImpl(begin, end) public fun LineSegment.line(algebra: GeometrySpace): Line = with(algebra) { Line(begin, end - begin) diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Polygon.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Polygon.kt new file mode 100644 index 000000000..20f4a031e --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Polygon.kt @@ -0,0 +1,14 @@ +/* + * 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.geometry + + +/** + * A closed polygon in 2D space + */ +public interface Polygon { + public val points: List> +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/projections.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/projections.kt index 6950abbdc..c5c3487a1 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/projections.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/projections.kt @@ -13,7 +13,7 @@ package space.kscience.kmath.geometry * @param line line to which vector should be projected */ public fun GeometrySpace.projectToLine(vector: V, line: Line): V = with(line) { - base + (direction dot (vector - base)) / (direction dot direction) * direction + start + (direction dot (vector - start)) / (direction dot direction) * direction } /** 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..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 @@ -57,23 +57,38 @@ 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)) + } + + @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) + } + } diff --git a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/ProjectionOntoLineTest.kt b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/ProjectionOntoLineTest.kt index cdb8ea870..7c6c105cf 100644 --- a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/ProjectionOntoLineTest.kt +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/ProjectionOntoLineTest.kt @@ -63,7 +63,7 @@ internal class ProjectionOntoLineTest { @Test fun projectionOntoLine3d() = with(Euclidean3DSpace) { - val line = Line3D( + val line = Line( base = vector(1.0, 3.5, 0.07), direction = vector(2.0, -0.0037, 11.1111) ) @@ -77,7 +77,7 @@ internal class ProjectionOntoLineTest { val result = projectToLine(v, line) // assert that result is on the line - assertTrue(isCollinear(result - line.base, line.direction)) + assertTrue(isCollinear(result - line.start, line.direction)) // assert that PV vector is orthogonal to direction vector assertTrue(isOrthogonal(v - result, line.direction)) } 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 3cd2212f6..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 @@ -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 @@ -23,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 @@ -33,8 +36,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 */ @@ -55,59 +56,75 @@ 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 { - 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 + + 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 + fromIndex - elementAlgebra.block(labelResolver(index)) - }.moveTo(fromIndex) + val index = it + startOffset + elementAlgebra.block(offsetToLabel(index)) + }.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(offsetToLabel) /** * Try to resolve element by label and return null if element with a given label is not found @@ -115,7 +132,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 +140,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 +151,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 +177,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) } @@ -174,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 { 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 diff --git a/kmath-trajectory/README.md b/kmath-trajectory/README.md deleted file mode 100644 index ac2930b04..000000000 --- a/kmath-trajectory/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# kmath-trajectory - - - - -## Artifact: - -The Maven coordinates of this project are `space.kscience:kmath-trajectory:0.3.1-dev-1`. - -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:kmath-trajectory:0.3.1-dev-1' -} -``` -**Gradle Kotlin DSL:** -```kotlin -repositories { - maven("https://repo.kotlin.link") - mavenCentral() -} - -dependencies { - implementation("space.kscience:kmath-trajectory:0.3.1-dev-1") -} -``` - -## Contributors -Erik Schouten (github: @ESchouten, email: erik-schouten@hotmail.nl) diff --git a/kmath-trajectory/build.gradle.kts b/kmath-trajectory/build.gradle.kts deleted file mode 100644 index 689c3265b..000000000 --- a/kmath-trajectory/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id("space.kscience.gradle.mpp") -} - -kscience{ - jvm() - js() - native() - - useContextReceivers() - useSerialization() - dependencies { - api(projects.kmath.kmathGeometry) - } -} - -readme { - description = "Path and trajectory optimization" - maturity = space.kscience.gradle.Maturity.PROTOTYPE - propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) -} diff --git a/kmath-trajectory/docs/README-TEMPLATE.md b/kmath-trajectory/docs/README-TEMPLATE.md deleted file mode 100644 index eb8e4a0c0..000000000 --- a/kmath-trajectory/docs/README-TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ -# kmath-trajectory - - -${features} - -${artifact} - -## Author -Erik Schouten - -Github: ESchouten - -Email: erik-schouten@hotmail.nl diff --git a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/DubinsPath.kt b/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/DubinsPath.kt deleted file mode 100644 index 568ef691a..000000000 --- a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/DubinsPath.kt +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2018-2022 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.trajectory - -import space.kscience.kmath.geometry.* -import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo -import kotlin.math.acos - -internal fun DubinsPose2D.getLeftCircle(radius: Double): Circle2D = getTangentCircles(radius).first - -internal fun DubinsPose2D.getRightCircle(radius: Double): Circle2D = getTangentCircles(radius).second - -internal fun DubinsPose2D.getTangentCircles(radius: Double): Pair = with(Euclidean2DSpace) { - val dX = radius * cos(bearing) - val dY = radius * sin(bearing) - return Circle2D(vector(x - dX, y + dY), radius) to Circle2D(vector(x + dX, y - dY), radius) -} - -internal fun leftOuterTangent(a: Circle2D, b: Circle2D): StraightTrajectory2D = - outerTangent(a, b, CircleTrajectory2D.Direction.LEFT) - -internal fun rightOuterTangent(a: Circle2D, b: Circle2D): StraightTrajectory2D = outerTangent( - a, b, - CircleTrajectory2D.Direction.RIGHT -) - -private fun outerTangent(a: Circle2D, b: Circle2D, side: CircleTrajectory2D.Direction): StraightTrajectory2D = - with(Euclidean2DSpace) { - val centers = StraightTrajectory2D(a.center, b.center) - val p1 = when (side) { - CircleTrajectory2D.Direction.LEFT -> vector( - a.center.x - a.radius * cos(centers.bearing), - a.center.y + a.radius * sin(centers.bearing) - ) - - CircleTrajectory2D.Direction.RIGHT -> vector( - a.center.x + a.radius * cos(centers.bearing), - a.center.y - a.radius * sin(centers.bearing) - ) - } - return StraightTrajectory2D( - p1, - vector(p1.x + (centers.end.x - centers.start.x), p1.y + (centers.end.y - centers.start.y)) - ) - } - -internal fun leftInnerTangent(base: Circle2D, direction: Circle2D): StraightTrajectory2D? = - innerTangent(base, direction, CircleTrajectory2D.Direction.LEFT) - -internal fun rightInnerTangent(base: Circle2D, direction: Circle2D): StraightTrajectory2D? = - innerTangent(base, direction, CircleTrajectory2D.Direction.RIGHT) - -private fun innerTangent( - base: Circle2D, - direction: Circle2D, - side: CircleTrajectory2D.Direction, -): StraightTrajectory2D? = - with(Euclidean2DSpace) { - val centers = StraightTrajectory2D(base.center, direction.center) - if (centers.length < base.radius * 2) return null - val angle = when (side) { - CircleTrajectory2D.Direction.LEFT -> centers.bearing + acos(base.radius * 2 / centers.length).radians - CircleTrajectory2D.Direction.RIGHT -> centers.bearing - acos(base.radius * 2 / centers.length).radians - }.normalized() - - val dX = base.radius * sin(angle) - val dY = base.radius * cos(angle) - val p1 = vector(base.center.x + dX, base.center.y + dY) - val p2 = vector(direction.center.x - dX, direction.center.y - dY) - return StraightTrajectory2D(p1, p2) - } - - -@Suppress("DuplicatedCode") -public object DubinsPath { - - public enum class Type { - RLR, LRL, RSR, LSL, RSL, LSR - } - - /** - * Return Dubins trajectory type or null if trajectory is not a Dubins path - */ - public fun trajectoryTypeOf(trajectory2D: CompositeTrajectory2D): Type? { - if (trajectory2D.segments.size != 3) return null - val a = trajectory2D.segments.first() as? CircleTrajectory2D ?: return null - val b = trajectory2D.segments[1] - val c = trajectory2D.segments.last() as? CircleTrajectory2D ?: return null - return Type.valueOf( - arrayOf( - a.direction.name[0], - if (b is CircleTrajectory2D) b.direction.name[0] else 'S', - c.direction.name[0] - ).toCharArray().concatToString() - ) - } - - public fun all( - start: DubinsPose2D, - end: DubinsPose2D, - turningRadius: Double, - ): List = listOfNotNull( - rlr(start, end, turningRadius), - lrl(start, end, turningRadius), - rsr(start, end, turningRadius), - lsl(start, end, turningRadius), - rsl(start, end, turningRadius), - lsr(start, end, turningRadius) - ) - - public fun shortest(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D = - all(start, end, turningRadius).minBy { it.length } - - public fun rlr(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D? = - with(Euclidean2DSpace) { - val c1 = start.getRightCircle(turningRadius) - val c2 = end.getRightCircle(turningRadius) - val centers = StraightTrajectory2D(c1.center, c2.center) - if (centers.length > turningRadius * 4) return null - - val firstVariant = run { - var theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized() - var dX = turningRadius * sin(theta) - var dY = turningRadius * cos(theta) - val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) - val e = Circle2D(p, turningRadius) - val p1 = vector(c1.center.x + dX, c1.center.y + dY) - theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized() - dX = turningRadius * sin(theta) - dY = turningRadius * cos(theta) - val p2 = vector(e.center.x + dX, e.center.y + dY) - val a1 = CircleTrajectory2D.of(c1.center, start, p1, CircleTrajectory2D.Direction.RIGHT) - val a2 = CircleTrajectory2D.of(e.center, p1, p2, CircleTrajectory2D.Direction.LEFT) - val a3 = CircleTrajectory2D.of(c2.center, p2, end, CircleTrajectory2D.Direction.RIGHT) - CompositeTrajectory2D(a1, a2, a3) - } - - val secondVariant = run { - var theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized() - var dX = turningRadius * sin(theta) - var dY = turningRadius * cos(theta) - val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) - val e = Circle2D(p, turningRadius) - val p1 = vector(c1.center.x + dX, c1.center.y + dY) - theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized() - dX = turningRadius * sin(theta) - dY = turningRadius * cos(theta) - val p2 = vector(e.center.x + dX, e.center.y + dY) - val a1 = CircleTrajectory2D.of(c1.center, start, p1, CircleTrajectory2D.Direction.RIGHT) - val a2 = CircleTrajectory2D.of(e.center, p1, p2, CircleTrajectory2D.Direction.LEFT) - val a3 = CircleTrajectory2D.of(c2.center, p2, end, CircleTrajectory2D.Direction.RIGHT) - CompositeTrajectory2D(a1, a2, a3) - } - - return if (firstVariant.length < secondVariant.length) firstVariant else secondVariant - } - - public fun lrl(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D? = - with(Euclidean2DSpace) { - val c1 = start.getLeftCircle(turningRadius) - val c2 = end.getLeftCircle(turningRadius) - val centers = StraightTrajectory2D(c1.center, c2.center) - if (centers.length > turningRadius * 4) return null - - val firstVariant = run { - var theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized() - var dX = turningRadius * sin(theta) - var dY = turningRadius * cos(theta) - val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) - val e = Circle2D(p, turningRadius) - val p1 = vector(c1.center.x + dX, c1.center.y + dY) - theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized() - dX = turningRadius * sin(theta) - dY = turningRadius * cos(theta) - val p2 = vector(e.center.x + dX, e.center.y + dY) - val a1 = CircleTrajectory2D.of(c1.center, start, p1, CircleTrajectory2D.Direction.LEFT) - val a2 = CircleTrajectory2D.of(e.center, p1, p2, CircleTrajectory2D.Direction.RIGHT) - val a3 = CircleTrajectory2D.of(c2.center, p2, end, CircleTrajectory2D.Direction.LEFT) - CompositeTrajectory2D(a1, a2, a3) - } - - val secondVariant = run { - var theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized() - var dX = turningRadius * sin(theta) - var dY = turningRadius * cos(theta) - val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) - val e = Circle2D(p, turningRadius) - val p1 = vector(c1.center.x + dX, c1.center.y + dY) - theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized() - dX = turningRadius * sin(theta) - dY = turningRadius * cos(theta) - val p2 = vector(e.center.x + dX, e.center.y + dY) - val a1 = CircleTrajectory2D.of(c1.center, start, p1, CircleTrajectory2D.Direction.LEFT) - val a2 = CircleTrajectory2D.of(e.center, p1, p2, CircleTrajectory2D.Direction.RIGHT) - val a3 = CircleTrajectory2D.of(c2.center, p2, end, CircleTrajectory2D.Direction.LEFT) - CompositeTrajectory2D(a1, a2, a3) - } - - return if (firstVariant.length < secondVariant.length) firstVariant else secondVariant - } - - public fun rsr(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D { - val c1 = start.getRightCircle(turningRadius) - val c2 = end.getRightCircle(turningRadius) - val s = leftOuterTangent(c1, c2) - val a1 = CircleTrajectory2D.of(c1.center, start, s.start, CircleTrajectory2D.Direction.RIGHT) - val a3 = CircleTrajectory2D.of(c2.center, s.end, end, CircleTrajectory2D.Direction.RIGHT) - return CompositeTrajectory2D(a1, s, a3) - } - - public fun lsl(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D { - val c1 = start.getLeftCircle(turningRadius) - val c2 = end.getLeftCircle(turningRadius) - val s = rightOuterTangent(c1, c2) - val a1 = CircleTrajectory2D.of(c1.center, start, s.start, CircleTrajectory2D.Direction.LEFT) - val a3 = CircleTrajectory2D.of(c2.center, s.end, end, CircleTrajectory2D.Direction.LEFT) - return CompositeTrajectory2D(a1, s, a3) - } - - public fun rsl(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D? { - val c1 = start.getRightCircle(turningRadius) - val c2 = end.getLeftCircle(turningRadius) - val s = rightInnerTangent(c1, c2) - if (s == null || c1.center.distanceTo(c2.center) < turningRadius * 2) return null - - val a1 = CircleTrajectory2D.of(c1.center, start, s.start, CircleTrajectory2D.Direction.RIGHT) - val a3 = CircleTrajectory2D.of(c2.center, s.end, end, CircleTrajectory2D.Direction.LEFT) - return CompositeTrajectory2D(a1, s, a3) - } - - public fun lsr(start: DubinsPose2D, end: DubinsPose2D, turningRadius: Double): CompositeTrajectory2D? { - val c1 = start.getLeftCircle(turningRadius) - val c2 = end.getRightCircle(turningRadius) - val s = leftInnerTangent(c1, c2) - if (s == null || c1.center.distanceTo(c2.center) < turningRadius * 2) return null - - val a1 = CircleTrajectory2D.of(c1.center, start, s.start, CircleTrajectory2D.Direction.LEFT) - val a3 = CircleTrajectory2D.of(c2.center, s.end, end, CircleTrajectory2D.Direction.RIGHT) - return CompositeTrajectory2D(a1, s, a3) - } -} - -public fun interface MaxCurvature { - public fun compute(startPoint: PhaseVector2D): Double -} - -public fun DubinsPath.shortest( - start: PhaseVector2D, - end: PhaseVector2D, - maxCurvature: MaxCurvature, -): CompositeTrajectory2D = shortest(start, end, maxCurvature.compute(start)) - diff --git a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/DubinsPose2D.kt b/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/DubinsPose2D.kt deleted file mode 100644 index 8362d0cb5..000000000 --- a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/DubinsPose2D.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2018-2022 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. - */ -@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class) -package space.kscience.kmath.trajectory - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import space.kscience.kmath.geometry.* -import kotlin.math.atan2 - -/** - * Combination of [Vector] and its view angle (clockwise from positive y-axis direction) - */ -@Serializable(DubinsPose2DSerializer::class) -public interface DubinsPose2D : DoubleVector2D { - public val coordinates: DoubleVector2D - public val bearing: Angle -} - -@Serializable -public class PhaseVector2D( - override val coordinates: DoubleVector2D, - public val velocity: DoubleVector2D, -) : DubinsPose2D, DoubleVector2D by coordinates { - override val bearing: Angle get() = atan2(velocity.x, velocity.y).radians -} - -@Serializable -@SerialName("DubinsPose2D") -private class DubinsPose2DImpl( - override val coordinates: DoubleVector2D, - override val bearing: Angle, -) : DubinsPose2D, DoubleVector2D by coordinates{ - - override fun toString(): String = "DubinsPose2D(x=$x, y=$y, bearing=$bearing)" -} - -public object DubinsPose2DSerializer: KSerializer{ - private val proxySerializer = DubinsPose2DImpl.serializer() - - override val descriptor: SerialDescriptor - get() = proxySerializer.descriptor - - override fun deserialize(decoder: Decoder): DubinsPose2D { - return decoder.decodeSerializableValue(proxySerializer) - } - - override fun serialize(encoder: Encoder, value: DubinsPose2D) { - val pose = value as? DubinsPose2DImpl ?: DubinsPose2DImpl(value.coordinates, value.bearing) - encoder.encodeSerializableValue(proxySerializer, pose) - } -} - -public fun DubinsPose2D(coordinate: DoubleVector2D, theta: Angle): DubinsPose2D = DubinsPose2DImpl(coordinate, theta) \ No newline at end of file diff --git a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/Trajectory2D.kt b/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/Trajectory2D.kt deleted file mode 100644 index e5f60e025..000000000 --- a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/Trajectory2D.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2018-2022 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. - */ -@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class) - -package space.kscience.kmath.trajectory - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import space.kscience.kmath.geometry.* -import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo -import kotlin.math.atan2 - -@Serializable -public sealed interface Trajectory2D { - public val length: Double -} - -/** - * Straight path segment. The order of start and end defines the direction - */ -@Serializable -@SerialName("straight") -public data class StraightTrajectory2D( - public val start: DoubleVector2D, - public val end: DoubleVector2D, -) : Trajectory2D { - override val length: Double get() = start.distanceTo(end) - - public val bearing: Angle get() = (atan2(end.x - start.x, end.y - start.y).radians).normalized() -} - -/** - * An arc segment - */ -@Serializable -@SerialName("arc") -public data class CircleTrajectory2D( - public val circle: Circle2D, - public val start: DubinsPose2D, - public val end: DubinsPose2D, -) : Trajectory2D { - - public enum class Direction { - LEFT, RIGHT - } - - /** - * Arc length in radians - */ - val arcLength: Angle - get() = if (direction == Direction.LEFT) { - start.bearing - end.bearing - } else { - end.bearing - start.bearing - }.normalized() - - - override val length: Double by lazy { - circle.radius * arcLength.radians - } - - public val direction: Direction by lazy { - if (start.y < circle.center.y) { - if (start.bearing > Angle.pi) Direction.RIGHT else Direction.LEFT - } else if (start.y > circle.center.y) { - if (start.bearing < Angle.pi) Direction.RIGHT else Direction.LEFT - } else { - if (start.bearing == Angle.zero) { - if (start.x < circle.center.x) Direction.RIGHT else Direction.LEFT - } else { - if (start.x > circle.center.x) Direction.RIGHT else Direction.LEFT - } - } - } - - public companion object { - public fun of( - center: DoubleVector2D, - start: DoubleVector2D, - end: DoubleVector2D, - direction: Direction, - ): CircleTrajectory2D { - fun calculatePose( - vector: DoubleVector2D, - theta: Angle, - direction: Direction, - ): DubinsPose2D = DubinsPose2D( - vector, - when (direction) { - Direction.LEFT -> (theta - Angle.piDiv2).normalized() - Direction.RIGHT -> (theta + Angle.piDiv2).normalized() - } - ) - - val s1 = StraightTrajectory2D(center, start) - val s2 = StraightTrajectory2D(center, end) - val pose1 = calculatePose(start, s1.bearing, direction) - val pose2 = calculatePose(end, s2.bearing, direction) - val trajectory = CircleTrajectory2D(Circle2D(center, s1.length), pose1, pose2) - if (trajectory.direction != direction) error("Trajectory direction mismatch") - return trajectory - } - } -} - -@Serializable -@SerialName("composite") -public class CompositeTrajectory2D(public val segments: List) : Trajectory2D { - override val length: Double get() = segments.sumOf { it.length } -} - -public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D = - CompositeTrajectory2D(segments.toList()) - diff --git a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/tangent.kt b/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/tangent.kt deleted file mode 100644 index d3165e162..000000000 --- a/kmath-trajectory/src/commonMain/kotlin/space/kscience/kmath/trajectory/tangent.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.trajectory - -import space.kscience.kmath.geometry.* -import kotlin.math.* - -/** - * Create inner and outer tangents between two circles. - * This method returns a map of segments using [DubinsPath] connection type notation. - */ -public fun Circle2D.tangentsToCircle( - other: Circle2D, -): Map> = with(Euclidean2DSpace) { - //return empty map for concentric circles - if(center.equalsVector(other.center)) return@tangentsToCircle emptyMap() - - // A line connecting centers - val line = LineSegment(center, other.center) - // Distance between centers - val distance = line.begin.distanceTo(line.end) - val angle1 = atan2(other.center.x - center.x, other.center.y - center.y) - var angle2: Double - val routes = mapOf( - DubinsPath.Type.RSR to Pair(radius, other.radius), - DubinsPath.Type.RSL to Pair(radius, -other.radius), - DubinsPath.Type.LSR to Pair(-radius, other.radius), - DubinsPath.Type.LSL to Pair(-radius, -other.radius) - ) - return buildMap { - for ((route, r1r2) in routes) { - val r1 = r1r2.first - val r2 = r1r2.second - val r = if (r1.sign == r2.sign) { - r1.absoluteValue - r2.absoluteValue - } else { - r1.absoluteValue + r2.absoluteValue - } - if (distance <= r) TODO("Intersecting circles are not supported yet") - val l = sqrt(distance * distance - r * r) - angle2 = if (r1.absoluteValue > r2.absoluteValue) { - angle1 + r1.sign * atan2(r.absoluteValue, l) - } else { - angle1 - r2.sign * atan2(r.absoluteValue, l) - } - val w = vector(-cos(angle2), sin(angle2)) - put( - route, - LineSegment( - center + w * r1, - other.center + w * r2 - ) - ) - - } - } -} \ No newline at end of file diff --git a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/TangentTest.kt b/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/TangentTest.kt deleted file mode 100644 index 6d4493124..000000000 --- a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/TangentTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.trajectory - -import space.kscience.kmath.geometry.Circle2D -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.Euclidean2DSpace.vector -import space.kscience.kmath.geometry.LineSegment -import space.kscience.kmath.geometry.equalsLine -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class TangentTest { - @Test - fun tangents() { - val c1 = Circle2D(vector(0.0, 0.0), 1.0) - val c2 = Circle2D(vector(4.0, 0.0), 1.0) - val routes = listOf( - DubinsPath.Type.RSR, - DubinsPath.Type.RSL, - DubinsPath.Type.LSR, - DubinsPath.Type.LSL - ) - val segments = listOf( - LineSegment( - begin = vector(0.0, 1.0), - end = vector(4.0, 1.0) - ), - LineSegment( - begin = vector(0.5, 0.8660254), - end = vector(3.5, -0.8660254) - ), - LineSegment( - begin = vector(0.5, -0.8660254), - end = vector(3.5, 0.8660254) - ), - LineSegment( - begin = vector(0.0, -1.0), - end = vector(4.0, -1.0) - ) - ) - - val tangentMap = c1.tangentsToCircle(c2) - val tangentMapKeys = tangentMap.keys.toList() - val tangentMapValues = tangentMap.values.toList() - - assertEquals(routes, tangentMapKeys) - for (i in segments.indices) { - assertTrue(segments[i].equalsLine(Euclidean2DSpace, tangentMapValues[i])) - } - } - - @Test - fun concentric(){ - val c1 = Circle2D(vector(0.0, 0.0), 10.0) - val c2 = Circle2D(vector(0.0, 0.0), 1.0) - assertEquals(emptyMap(), c1.tangentsToCircle(c2)) - } -// -// @Test -// fun nonExistingTangents() { -// assertFailsWith { -// val c1 = Circle2D(vector(0.0, 0.0), 1.0) -// val c2 = Circle2D(vector(2.0, 0.0), 1.0) -// c1.tangentsToCircle(c2) -// } -// assertFailsWith { -// val c1 = Circle2D(vector(0.0, 0.0), 1.0) -// val c2 = Circle2D(vector(0.5, 0.0), 1.0) -// c1.tangentsToCircle(c2) -// } -// } -} \ No newline at end of file diff --git a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/dubins/DubinsTests.kt b/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/dubins/DubinsTests.kt deleted file mode 100644 index 481ea4786..000000000 --- a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/dubins/DubinsTests.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2018-2022 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.trajectory.dubins - -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.equalsFloat -import space.kscience.kmath.trajectory.* -import kotlin.test.Test -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - - -class DubinsTests { - - @Test - fun dubinsTest() = with(Euclidean2DSpace){ - val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0)) - val lineP1 = straight.shift(1, 10.0).inverse() - - val start = DubinsPose2D(straight.end, straight.bearing) - val end = DubinsPose2D(lineP1.start, lineP1.bearing) - val radius = 2.0 - val dubins = DubinsPath.all(start, end, radius) - - val absoluteDistance = start.distanceTo(end) - println("Absolute distance: $absoluteDistance") - - val expectedLengths = mapOf( - DubinsPath.Type.RLR to 13.067681939031397, - DubinsPath.Type.RSR to 12.28318530717957, - DubinsPath.Type.LSL to 32.84955592153878, - DubinsPath.Type.RSL to 23.37758938854081, - DubinsPath.Type.LSR to 23.37758938854081 - ) - - expectedLengths.forEach { - val path = dubins.find { p -> DubinsPath.trajectoryTypeOf(p) === it.key } - assertNotNull(path, "Path ${it.key} not found") - println("${it.key}: ${path.length}") - assertTrue(it.value.equalsFloat(path.length)) - - val a = path.segments[0] as CircleTrajectory2D - val b = path.segments[1] - val c = path.segments[2] as CircleTrajectory2D - - assertTrue(start.equalsFloat(a.start)) - assertTrue(end.equalsFloat(c.end)) - - // Not working, theta double precision inaccuracy - if (b is CircleTrajectory2D) { - assertTrue(a.end.equalsFloat(b.start)) - assertTrue(c.start.equalsFloat(b.end)) - } else if (b is StraightTrajectory2D) { - assertTrue(a.end.equalsFloat(DubinsPose2D(b.start, b.bearing))) - assertTrue(c.start.equalsFloat(DubinsPose2D(b.end, b.bearing))) - } - } - } -} diff --git a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/math.kt b/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/math.kt deleted file mode 100644 index 24685f528..000000000 --- a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/math.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2018-2022 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.trajectory - -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.equalsFloat -import space.kscience.kmath.geometry.radians -import space.kscience.kmath.geometry.sin - - -fun DubinsPose2D.equalsFloat(other: DubinsPose2D) = - x.equalsFloat(other.x) && y.equalsFloat(other.y) && bearing.radians.equalsFloat(other.bearing.radians) - -fun StraightTrajectory2D.inverse() = StraightTrajectory2D(end, start) - -fun StraightTrajectory2D.shift(shift: Int, width: Double): StraightTrajectory2D = with(Euclidean2DSpace) { - val dX = width * sin(inverse().bearing) - val dY = width * sin(bearing) - - return StraightTrajectory2D( - vector(start.x - dX * shift, start.y - dY * shift), - vector(end.x - dX * shift, end.y - dY * shift) - ) -} diff --git a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/ArcTests.kt b/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/ArcTests.kt deleted file mode 100644 index 7594aa046..000000000 --- a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/ArcTests.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2018-2022 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.trajectory.segments - -import space.kscience.kmath.geometry.Circle2D -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.circumference -import space.kscience.kmath.geometry.degrees -import space.kscience.kmath.trajectory.CircleTrajectory2D -import kotlin.test.Test -import kotlin.test.assertEquals - -class ArcTests { - - @Test - fun arcTest() = with(Euclidean2DSpace){ - val circle = Circle2D(vector(0.0, 0.0), 2.0) - val arc = CircleTrajectory2D.of( - circle.center, - vector(-2.0, 0.0), - vector(0.0, 2.0), - CircleTrajectory2D.Direction.RIGHT - ) - assertEquals(circle.circumference / 4, arc.length, 1.0) - assertEquals(0.0, arc.start.bearing.degrees) - assertEquals(90.0, arc.end.bearing.degrees) - } -} diff --git a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/CircleTests.kt b/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/CircleTests.kt deleted file mode 100644 index c3fca06ec..000000000 --- a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/CircleTests.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2018-2022 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.trajectory.segments - -import space.kscience.kmath.geometry.Circle2D -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.circumference -import kotlin.test.Test -import kotlin.test.assertEquals - -class CircleTests { - - @Test - fun arcTest() { - val center = Euclidean2DSpace.vector(0.0, 0.0) - val radius = 2.0 - val expectedCircumference = 12.56637 - val circle = Circle2D(center, radius) - assertEquals(expectedCircumference, circle.circumference, 1e-4) - } -} diff --git a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/LineTests.kt b/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/LineTests.kt deleted file mode 100644 index 54deb2193..000000000 --- a/kmath-trajectory/src/commonTest/kotlin/space/kscience/kmath/trajectory/segments/LineTests.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2018-2022 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.trajectory.segments - -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.degrees -import space.kscience.kmath.trajectory.StraightTrajectory2D -import kotlin.math.pow -import kotlin.math.sqrt -import kotlin.test.Test -import kotlin.test.assertEquals - -class LineTests { - - @Test - fun lineTest() = with(Euclidean2DSpace){ - val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0)) - assertEquals(sqrt(100.0.pow(2) + 100.0.pow(2)), straight.length) - assertEquals(45.0, straight.bearing.degrees) - } - - @Test - fun lineAngleTest() = with(Euclidean2DSpace){ - //val zero = Vector2D(0.0, 0.0) - val north = StraightTrajectory2D(zero, vector(0.0, 2.0)) - assertEquals(0.0, north.bearing.degrees) - val east = StraightTrajectory2D(zero, vector(2.0, 0.0)) - assertEquals(90.0, east.bearing.degrees) - val south = StraightTrajectory2D(zero, vector(0.0, -2.0)) - assertEquals(180.0, south.bearing.degrees) - val west = StraightTrajectory2D(zero, vector(-2.0, 0.0)) - assertEquals(270.0, west.bearing.degrees) - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7d92dc36e..f158f3444 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,7 +44,6 @@ include( ":kmath-jupyter", ":kmath-symja", ":kmath-jafama", - ":kmath-trajectory", ":examples", ":benchmarks", )