Merge branch 'dev-local' into mrfendel

# Conflicts:
#	kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/SeriesAlgebra.kt
This commit is contained in:
mrFendel 2023-04-07 10:55:25 +03:00
commit 165dfd6c5d
35 changed files with 333 additions and 947 deletions

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: actions/checkout@v3.0.0 - uses: actions/checkout@v3.0.0
- uses: actions/setup-java@v3.0.0 - uses: actions/setup-java@v3.10.0
with: with:
java-version: 11 java-version: 11
distribution: liberica distribution: liberica
@ -26,26 +26,25 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- uses: gradle/wrapper-validation-action@v1.0.4
- name: Publish Windows Artifacts - name: Publish Windows Artifacts
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
uses: gradle/gradle-build-action@v2.1.5 uses: gradle/gradle-build-action@v2.4.0
with: with:
arguments: | arguments: |
releaseAll publishAllPublicationsToSpaceRepository
-Ppublishing.enabled=true -Ppublishing.targets=all
-Ppublishing.sonatype=false
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}
- name: Publish Mac Artifacts - name: Publish Mac Artifacts
if: matrix.os == 'macOS-latest' if: matrix.os == 'macOS-latest'
uses: gradle/gradle-build-action@v2.1.5 uses: gradle/gradle-build-action@v2.4.0
with: with:
arguments: | arguments: |
releaseMacosX64 publishMacosX64PublicationToSpaceRepository
releaseIosArm64 publishMacosArm64PublicationToSpaceRepository
releaseIosX64 publishIosX64PublicationToSpaceRepository
-Ppublishing.enabled=true publishIosArm64PublicationToSpaceRepository
-Ppublishing.sonatype=false publishIosSimulatorArm64PublicationToSpaceRepository
-Ppublishing.targets=all
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }} -Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }} -Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}

3
.gitignore vendored
View File

@ -18,4 +18,5 @@ out/
!/.idea/copyright/ !/.idea/copyright/
!/.idea/scopes/ !/.idea/scopes/
/kotlin-js-store/yarn.lock /gradle/yarn.lock

View File

@ -2,6 +2,7 @@
## [Unreleased] ## [Unreleased]
### Added ### Added
- Generic builders for `BufferND` and `MutableBufferND`
- `NamedMatrix` - matrix with symbol-based indexing - `NamedMatrix` - matrix with symbol-based indexing
- `Expression` with default arguments - `Expression` with default arguments
- Type-aliases for numbers like `Float64` - Type-aliases for numbers like `Float64`
@ -23,6 +24,7 @@
### Deprecated ### Deprecated
### Removed ### Removed
- Trajectory moved to https://github.com/SciProgCentre/maps-kt
- Polynomials moved to https://github.com/SciProgCentre/kmath-polynomial - Polynomials moved to https://github.com/SciProgCentre/kmath-polynomial
### Fixed ### Fixed

View File

@ -15,7 +15,7 @@ allprojects {
} }
group = "space.kscience" group = "space.kscience"
version = "0.3.1-dev-10" version = "0.3.1-dev-11"
} }
subprojects { subprojects {

View File

@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins { plugins {
kotlin("jvm") kotlin("jvm")
} }
@ -33,6 +35,8 @@ dependencies {
implementation(project(":kmath-multik")) implementation(project(":kmath-multik"))
implementation("org.jetbrains.kotlinx:multik-default:$multikVersion") 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") implementation("org.nd4j:nd4j-native:1.0.0-beta7")
@ -46,25 +50,24 @@ dependencies {
// } else // } else
implementation("org.nd4j:nd4j-native-platform:1.0.0-beta7") 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") implementation("org.slf4j:slf4j-simple:1.7.32")
// plotting // plotting
implementation("space.kscience:plotlykt-server:0.5.0") implementation("space.kscience:plotlykt-server:0.5.0")
} }
kotlin.sourceSets.all { kotlin {
jvmToolchain(11)
sourceSets.all {
with(languageSettings) { with(languageSettings) {
optIn("kotlin.contracts.ExperimentalContracts") optIn("kotlin.contracts.ExperimentalContracts")
optIn("kotlin.ExperimentalUnsignedTypes") optIn("kotlin.ExperimentalUnsignedTypes")
optIn("space.kscience.kmath.misc.UnstableKMathAPI") optIn("space.kscience.kmath.misc.UnstableKMathAPI")
} }
} }
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> { tasks.withType<KotlinJvmCompile> {
kotlinOptions { kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy"
} }
} }

View File

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

View File

@ -9,7 +9,7 @@ kotlin.native.ignoreDisabledTargets=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.14.2-kotlin-1.8.10 toolsVersion=0.14.6-kotlin-1.8.20
org.gradle.parallel=true org.gradle.parallel=true

View File

@ -9,7 +9,7 @@ import kotlin.jvm.JvmInline
import kotlin.reflect.KClass 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<F : Any> { public interface Featured<F : Any> {
public fun <T : F> getFeature(type: FeatureKey<T>): T? public fun <T : F> getFeature(type: FeatureKey<T>): T?

View File

@ -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 <T, R> List<T>.zipWithNextCircular(transform: (a: T, b: T) -> R): List<R> {
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 <T> List<T>.zipWithNextCircular(): List<Pair<T,T>> = zipWithNextCircular { l, r -> l to r }

View File

@ -7,7 +7,9 @@ package space.kscience.kmath.nd
import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.MutableBuffer import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.structures.MutableBufferFactory
/** /**
* Represents [StructureND] over [Buffer]. * Represents [StructureND] over [Buffer].
@ -29,6 +31,18 @@ public open class BufferND<out T>(
override fun toString(): String = StructureND.toString(this) override fun toString(): String = StructureND.toString(this)
} }
/**
* Create a generic [BufferND] using provided [initializer]
*/
public fun <T> BufferND(
shape: ShapeND,
bufferFactory: BufferFactory<T> = BufferFactory.boxing(),
initializer: (IntArray) -> T,
): BufferND<T> {
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] // * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferND]
// */ // */
@ -67,6 +81,18 @@ public open class MutableBufferND<T>(
} }
} }
/**
* Create a generic [BufferND] using provided [initializer]
*/
public fun <T> MutableBufferND(
shape: ShapeND,
bufferFactory: MutableBufferFactory<T> = MutableBufferFactory.boxing(),
initializer: (IntArray) -> T,
): MutableBufferND<T> {
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] // * Transform structure to a new structure using provided [MutableBufferFactory] and optimizing if argument is [MutableBufferND]
// */ // */

View File

@ -8,6 +8,7 @@
package space.kscience.kmath.coroutines package space.kscience.kmath.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.produce import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -57,7 +58,7 @@ public suspend fun <T> AsyncFlow<T>.collect(concurrency: Int, collector: FlowCol
coroutineScope { coroutineScope {
//Starting up to N deferred coroutines ahead of time //Starting up to N deferred coroutines ahead of time
val channel = produce(capacity = concurrency - 1) { val channel: ReceiveChannel<LazyDeferred<T>> = produce(capacity = concurrency - 1) {
deferredFlow.collect { value -> deferredFlow.collect { value ->
value.start(this@coroutineScope) value.start(this@coroutineScope)
send(value) send(value)

View File

@ -9,7 +9,6 @@ import space.kscience.kmath.operations.mapToBuffer
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asBuffer import space.kscience.kmath.structures.asBuffer
import kotlin.jvm.Synchronized
import kotlin.math.ulp import kotlin.math.ulp
import kotlin.native.concurrent.ThreadLocal import kotlin.native.concurrent.ThreadLocal
@ -57,7 +56,6 @@ public object GaussLegendreRuleFactory : GaussIntegratorRuleFactory {
private val cache = HashMap<Int, Pair<Buffer<Double>, Buffer<Double>>>() private val cache = HashMap<Int, Pair<Buffer<Double>, Buffer<Double>>>()
@Synchronized
private fun getOrBuildRule(numPoints: Int): Pair<Buffer<Double>, Buffer<Double>> = private fun getOrBuildRule(numPoints: Int): Pair<Buffer<Double>, Buffer<Double>> =
cache.getOrPut(numPoints) { buildRule(numPoints) } cache.getOrPut(numPoints) { buildRule(numPoints) }

View File

@ -78,8 +78,11 @@ public object Euclidean3DSpace : GeometrySpace<DoubleVector3D>, 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 = 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) } override val zero: DoubleVector3D by lazy { vector(0.0, 0.0, 0.0) }
@ -100,6 +103,46 @@ public object Euclidean3DSpace : GeometrySpace<DoubleVector3D>, ScaleOperations<
override fun DoubleVector3D.dot(other: DoubleVector3D): Double = override fun DoubleVector3D.dot(other: DoubleVector3D): Double =
x * other.x + y * other.y + z * other.z 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<Double> = vectorProduct(this, other)
public val xAxis: DoubleVector3D = vector(1.0, 0.0, 0.0) public val xAxis: DoubleVector3D = vector(1.0, 0.0, 0.0)
public val yAxis: DoubleVector3D = vector(0.0, 1.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) public val zAxis: DoubleVector3D = vector(0.0, 0.0, 1.0)

View File

@ -5,14 +5,23 @@
package space.kscience.kmath.geometry package space.kscience.kmath.geometry
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable 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 * but its length does not affect line properties
*/ */
public interface Line<out V : Vector> {
public val start: V
public val direction: V
}
@Serializable @Serializable
public data class Line<out V : Vector>(val base: V, val direction: V) @SerialName("Line")
private data class LineImpl<out V : Vector>(override val start: V, override val direction: V): Line<V>
public fun <V : Vector> Line(base: V, direction: V): Line<V> = LineImpl(base, direction)
public typealias Line2D = Line<DoubleVector2D> public typealias Line2D = Line<DoubleVector2D>
public typealias Line3D = Line<DoubleVector3D> public typealias Line3D = Line<DoubleVector3D>
@ -20,8 +29,19 @@ public typealias Line3D = Line<DoubleVector3D>
/** /**
* A directed line segment between [begin] and [end] * A directed line segment between [begin] and [end]
*/ */
public interface LineSegment<out V : Vector> {
public val begin: V
public val end: V
}
/**
* Basic implementation for [LineSegment]
*/
@Serializable @Serializable
public data class LineSegment<out V : Vector>(val begin: V, val end: V) @SerialName("LineSegment")
private data class LineSegmentImpl<out V : Vector>(override val begin: V, override val end: V) : LineSegment<V>
public fun <V : Vector> LineSegment(begin: V, end: V): LineSegment<V> = LineSegmentImpl(begin, end)
public fun <V : Vector> LineSegment<V>.line(algebra: GeometrySpace<V>): Line<V> = with(algebra) { public fun <V : Vector> LineSegment<V>.line(algebra: GeometrySpace<V>): Line<V> = with(algebra) {
Line(begin, end - begin) Line(begin, end - begin)

View File

@ -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<T> {
public val points: List<Vector2D<T>>
}

View File

@ -13,7 +13,7 @@ package space.kscience.kmath.geometry
* @param line line to which vector should be projected * @param line line to which vector should be projected
*/ */
public fun <V : Vector> GeometrySpace<V>.projectToLine(vector: V, line: Line<V>): V = with(line) { public fun <V : Vector> GeometrySpace<V>.projectToLine(vector: V, line: Line<V>): V = with(line) {
base + (direction dot (vector - base)) / (direction dot direction) * direction start + (direction dot (vector - start)) / (direction dot direction) * direction
} }
/** /**

View File

@ -57,8 +57,7 @@ internal class Euclidean3DSpaceTest {
} }
@Test @Test
fun add() { fun add() = with(Euclidean3DSpace) {
with(Euclidean3DSpace) {
assertVectorEquals( assertVectorEquals(
vector(1.0, -2.0, 0.001), vector(1.0, -2.0, 0.001),
vector(1.0, -2.0, 0.001) + zero vector(1.0, -2.0, 0.001) + zero
@ -68,12 +67,28 @@ internal class Euclidean3DSpaceTest {
vector(1.0, 2.0, 3.0) + vector(7.0, -5.0, 0.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)
} }
@Test @Test
fun multiply() { fun vectorProduct() = with(Euclidean3DSpace) {
with(Euclidean3DSpace) { assertVectorEquals(zAxis, vectorProduct(xAxis, yAxis))
assertVectorEquals(vector(2.0, -4.0, 0.0), vector(1.0, -2.0, 0.0) * 2) 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)
} }
} }

View File

@ -63,7 +63,7 @@ internal class ProjectionOntoLineTest {
@Test @Test
fun projectionOntoLine3d() = with(Euclidean3DSpace) { fun projectionOntoLine3d() = with(Euclidean3DSpace) {
val line = Line3D( val line = Line(
base = vector(1.0, 3.5, 0.07), base = vector(1.0, 3.5, 0.07),
direction = vector(2.0, -0.0037, 11.1111) direction = vector(2.0, -0.0037, 11.1111)
) )
@ -77,7 +77,7 @@ internal class ProjectionOntoLineTest {
val result = projectToLine(v, line) val result = projectToLine(v, line)
// assert that result is on the 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 // assert that PV vector is orthogonal to direction vector
assertTrue(isOrthogonal(v - result, line.direction)) assertTrue(isOrthogonal(v - result, line.direction))
} }

View File

@ -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<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L : Comparable<L>>(
bufferAlgebra: BA,
offsetToLabel: (Int) -> L,
private val labelToOffset: (L) -> Double,
) : SeriesAlgebra<T, A, BA, L>(bufferAlgebra, offsetToLabel) {
public val Buffer<T>.labelRange: ClosedRange<L> 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<T>.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<T>.getByLabel(label: L): T = getByLabelOrNull(label)
?: throw IndexOutOfBoundsException("Label $label is not in $labelRange")
}

View File

@ -3,21 +3,17 @@ package space.kscience.kmath.series
import space.kscience.kmath.operations.BufferAlgebra import space.kscience.kmath.operations.BufferAlgebra
import space.kscience.kmath.operations.Ring import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.RingOps import space.kscience.kmath.operations.RingOps
import space.kscience.kmath.operations.reduce
import space.kscience.kmath.stat.StatisticalAlgebra import space.kscience.kmath.stat.StatisticalAlgebra
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.BufferView import space.kscience.kmath.structures.BufferView
import space.kscience.kmath.structures.getOrNull
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
// TODO: check if ranges are intersected
@PublishedApi @PublishedApi
internal fun IntRange.intersect(other: IntRange): IntRange = internal fun IntRange.intersect(other: IntRange): IntRange =
max(first, other.first)..min(last, other.last) max(first, other.first)..min(last, other.last)
@PublishedApi @PublishedApi
internal val IntRange.size: Int internal val IntRange.size: Int
get() = last - first + 1 get() = last - first + 1
@ -28,7 +24,9 @@ internal operator fun IntRange.contains(other: IntRange): Boolean = (other.first
//TODO add permutation sort //TODO add permutation sort
//TODO check rank statistics //TODO check rank statistics
/**
* A [Buffer] with an offset relative to the [SeriesAlgebra] zero.
*/
public interface Series<T> : Buffer<T> { public interface Series<T> : Buffer<T> {
public val origin: Buffer<T> public val origin: Buffer<T>
@ -38,14 +36,9 @@ public interface Series<T> : Buffer<T> {
public val position: Int public val position: Int
} }
public val <T> Series<T>.absoluteIndices: IntRange get() = position until position + size
/** /**
* A [BufferView] with index offset (both positive and negative) and possible size change * A [BufferView] with index offset (both positive and negative) and possible size change
*/ */
private class SeriesImpl<T>( private class SeriesImpl<T>(
override val origin: Buffer<T>, override val origin: Buffer<T>,
override val position: Int, override val position: Int,
@ -63,61 +56,75 @@ private class SeriesImpl<T>(
/** /**
* A scope to operation on series * A scope to operation on series
*/ */
public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>( public open class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
override val bufferAlgebra: BA, override val bufferAlgebra: BA,
private val labelResolver: (Int) -> L, public val offsetToLabel: (Int) -> L,
) : RingOps<Buffer<T>>, StatisticalAlgebra<T, A, BA> { ) : RingOps<Buffer<T>>, StatisticalAlgebra<T, A, BA> {
public val Buffer<T>.indices: IntRange /**
* A range of valid offset indices. In general, does not start with zero.
*/
public val Buffer<T>.offsetIndices: IntRange
get() = if (this is Series) { get() = if (this is Series) {
absoluteIndices position until position + size
} else { } else {
0 until size 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<T>.getAbsoluteOrNull(index: Int): T? = when { public fun Buffer<T>.getByOffsetOrNull(index: Int): T? = when {
index !in indices -> null index !in offsetIndices -> null
this is Series -> origin[index - position] this is Series -> origin.getOrNull(index - position)
else -> get(index) else -> getOrNull(index)
} }
/** /**
* Get the value by absolute index in the series algebra or throw [IndexOutOfBoundsException] if index is out of range * Get the value by absolute index in the series algebra or throw [IndexOutOfBoundsException] if index is out of range
*/ */
public fun Buffer<T>.getAbsolute(index: Int): T = public fun Buffer<T>.getByOffset(index: Int): T =
getAbsoluteOrNull(index) ?: throw IndexOutOfBoundsException("Index $index is not in $indices") 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<T>.moveTo(index: Int): Series<T> = if (this is Series) { public fun Buffer<T>.moveTo(position: Int): Series<T> = if (this is Series) {
SeriesImpl(origin, index, size) SeriesImpl(origin, position, size)
} else { } else {
SeriesImpl(this, index, size) SeriesImpl(this, position, size)
} }
public val Buffer<T>.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<T>.moveBy(offset: Int): Series<T> = 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<T> { public val Buffer<T>.startOffset: Int get() = if (this is Series) position else 0
public val Buffer<T>.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<T> {
return elementAlgebra.bufferFactory(size) { return elementAlgebra.bufferFactory(size) {
val index = it + fromIndex val index = it + startOffset
elementAlgebra.block(labelResolver(index)) elementAlgebra.block(offsetToLabel(index))
}.moveTo(fromIndex) }.moveTo(startOffset)
} }
/** /**
* Get a label buffer for given buffer. * Get a label buffer for given buffer.
*/ */
public val Buffer<T>.labels: List<L> get() = indices.map(labelResolver) public val Buffer<T>.labels: List<L> get() = offsetIndices.map(offsetToLabel)
// TODO: there can be troubles with label consistency after moving position argument
// TODO: so offset should be reflected in the labelResolver also
/** /**
* Try to resolve element by label and return null if element with a given label is not found * Try to resolve element by label and return null if element with a given label is not found
@ -125,7 +132,7 @@ public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
public operator fun Buffer<T>.get(label: L): T? { public operator fun Buffer<T>.get(label: L): T? {
val index = labels.indexOf(label) val index = labels.indexOf(label)
if (index == -1) return null if (index == -1) return null
return getAbsolute(index + offset) return getByOffset(index + startOffset)
} }
/** /**
@ -133,9 +140,9 @@ public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
*/ */
public inline fun Buffer<T>.map(crossinline transform: A.(T) -> T): Series<T> { public inline fun Buffer<T>.map(crossinline transform: A.(T) -> T): Series<T> {
val buf = elementAlgebra.bufferFactory(size) { val buf = elementAlgebra.bufferFactory(size) {
elementAlgebra.transform(getAbsolute(it)) elementAlgebra.transform(getByOffset(it))
} }
return buf.moveTo(indices.first) return buf.moveTo(offsetIndices.first)
} }
/** /**
@ -144,59 +151,48 @@ public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
public inline fun Buffer<T>.mapWithLabel(crossinline transform: A.(arg: T, label: L) -> T): Series<T> { public inline fun Buffer<T>.mapWithLabel(crossinline transform: A.(arg: T, label: L) -> T): Series<T> {
val labels = labels val labels = labels
val buf = elementAlgebra.bufferFactory(size) { 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 <R> Buffer<T>.fold(initial: R, operation: A.(acc: R, T) -> R): R { public inline fun <R> Buffer<T>.fold(initial: R, operation: A.(acc: R, T) -> R): R {
var accumulator = initial 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 return accumulator
} }
public inline fun <R> Buffer<T>.foldWithLabel(initial: R, operation: A.(acc: R, arg: T, label: L) -> R): R { public inline fun <R> Buffer<T>.foldWithLabel(initial: R, operation: A.(acc: R, arg: T, label: L) -> R): R {
val labels = labels val labels = labels
var accumulator = initial var accumulator = initial
for (index in this.indices) accumulator = for (index in this.offsetIndices) accumulator =
elementAlgebra.operation(accumulator, getAbsolute(index), labels[index]) elementAlgebra.operation(accumulator, getByOffset(index), labels[index])
return accumulator return accumulator
} }
/** /**
* Zip two buffers in the range where they overlap * Zip two buffers in the range whe they overlap
*/ */
public inline fun Buffer<T>.zip( public inline fun Buffer<T>.zip(
other: Buffer<T>, other: Buffer<T>,
crossinline operation: A.(left: T, right: T) -> T, crossinline operation: A.(left: T, right: T) -> T,
): Series<T> { ): Series<T> {
val newRange = indices.intersect(other.indices) val newRange = offsetIndices.intersect(other.offsetIndices)
return elementAlgebra.bufferFactory(newRange.size) { return elementAlgebra.bufferFactory(newRange.size) {
elementAlgebra.operation( elementAlgebra.operation(
getAbsolute(it), getByOffset(it),
other.getAbsolute(it) other.getByOffset(it)
) )
}.moveTo(newRange.first) }.moveTo(newRange.first)
} }
/**
* Zip buffer with itself, but shifted
* */
public inline fun Buffer<T>.shiftOp(
shift: Int = 1,
crossinline operation: A.(left: T, right: T) -> T
): Buffer<T> {
val shifted = this.moveTo(this.offset+shift)
return zip(shifted, operation)
}
override fun Buffer<T>.unaryMinus(): Buffer<T> = map { -it } override fun Buffer<T>.unaryMinus(): Buffer<T> = map { -it }
override fun add(left: Buffer<T>, right: Buffer<T>): Series<T> = left.zip(right) { l, r -> l + r } override fun add(left: Buffer<T>, right: Buffer<T>): Series<T> = left.zip(right) { l, r -> l + r }
override fun multiply(left: Buffer<T>, right: Buffer<T>): Buffer<T> = left.zip(right) { l, r -> l * r } override fun multiply(left: Buffer<T>, right: Buffer<T>): Buffer<T> = left.zip(right) { l, r -> l * r }
public inline fun Buffer<T>.diff(): Buffer<T> = this.shiftOp {l, r -> r - l} public companion object
} }
public fun <T, A : Ring<T>, BA : BufferAlgebra<T, A>, L> BA.seriesAlgebra(labels: Iterable<L>): SeriesAlgebra<T, A, BA, L> { public fun <T, A : Ring<T>, BA : BufferAlgebra<T, A>, L> BA.seriesAlgebra(labels: Iterable<L>): SeriesAlgebra<T, A, BA, L> {

View File

@ -12,32 +12,32 @@ import space.kscience.kmath.structures.Buffer
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sin( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sin(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.sin(arg).moveTo(arg.offset) bufferAlgebra.sin(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.cos( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.cos(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.cos(arg).moveTo(arg.offset) bufferAlgebra.cos(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.tan( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.tan(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.tan(arg).moveTo(arg.offset) bufferAlgebra.tan(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.asin( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.asin(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.asin(arg).moveTo(arg.offset) bufferAlgebra.asin(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.acos( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.acos(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.acos(arg).moveTo(arg.offset) bufferAlgebra.acos(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atan( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atan(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : TrigonometricOperations<Buffer<T>> =
bufferAlgebra.atan(arg).moveTo(arg.offset) bufferAlgebra.atan(arg).moveTo(arg.startOffset)
//exponential //exponential
@ -45,42 +45,42 @@ public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atan(
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.exp( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.exp(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.exp(arg).moveTo(arg.offset) bufferAlgebra.exp(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.ln( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.ln(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.ln(arg).moveTo(arg.offset) bufferAlgebra.ln(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sinh( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sinh(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.sinh(arg).moveTo(arg.offset) bufferAlgebra.sinh(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.cosh( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.cosh(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.cosh(arg).moveTo(arg.offset) bufferAlgebra.cosh(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.tanh( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.tanh(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.tanh(arg).moveTo(arg.offset) bufferAlgebra.tanh(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.asinh( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.asinh(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.asinh(arg).moveTo(arg.offset) bufferAlgebra.asinh(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.acosh( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.acosh(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.acosh(arg).moveTo(arg.offset) bufferAlgebra.acosh(arg).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atanh( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.atanh(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : ExponentialOperations<Buffer<T>> =
bufferAlgebra.atanh(arg).moveTo(arg.offset) bufferAlgebra.atanh(arg).moveTo(arg.startOffset)
//power //power
@ -89,9 +89,9 @@ public fun <T, BA> SeriesAlgebra<T, *, BA, *>.power(
arg: Buffer<T>, arg: Buffer<T>,
pow: Number, pow: Number,
): Series<T> where BA : BufferAlgebra<T, *>, BA : PowerOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : PowerOperations<Buffer<T>> =
bufferAlgebra.power(arg, pow).moveTo(arg.offset) bufferAlgebra.power(arg, pow).moveTo(arg.startOffset)
public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sqrt( public fun <T, BA> SeriesAlgebra<T, *, BA, *>.sqrt(
arg: Buffer<T>, arg: Buffer<T>,
): Series<T> where BA : BufferAlgebra<T, *>, BA : PowerOperations<Buffer<T>> = ): Series<T> where BA : BufferAlgebra<T, *>, BA : PowerOperations<Buffer<T>> =
bufferAlgebra.sqrt(arg).moveTo(arg.offset) bufferAlgebra.sqrt(arg).moveTo(arg.startOffset)

View File

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

View File

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

View File

@ -1,13 +0,0 @@
# kmath-trajectory
${features}
${artifact}
## Author
Erik Schouten
Github: ESchouten
Email: erik-schouten@hotmail.nl

View File

@ -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<Circle2D, Circle2D> = 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<CompositeTrajectory2D> = 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))

View File

@ -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<DubinsPose2D>{
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)

View File

@ -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>) : Trajectory2D {
override val length: Double get() = segments.sumOf { it.length }
}
public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D =
CompositeTrajectory2D(segments.toList())

View File

@ -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<DubinsPath.Type, LineSegment<DoubleVector2D>> = 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
)
)
}
}
}

View File

@ -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<NotImplementedError> {
// val c1 = Circle2D(vector(0.0, 0.0), 1.0)
// val c2 = Circle2D(vector(2.0, 0.0), 1.0)
// c1.tangentsToCircle(c2)
// }
// assertFailsWith<NotImplementedError> {
// val c1 = Circle2D(vector(0.0, 0.0), 1.0)
// val c2 = Circle2D(vector(0.5, 0.0), 1.0)
// c1.tangentsToCircle(c2)
// }
// }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,6 @@ include(
":kmath-jupyter", ":kmath-jupyter",
":kmath-symja", ":kmath-symja",
":kmath-jafama", ":kmath-jafama",
":kmath-trajectory",
":examples", ":examples",
":benchmarks", ":benchmarks",
) )