forked from kscience/kmath
Merge branch 'dev' into ejeny_branch_
This commit is contained in:
commit
74a550effd
23
.github/workflows/publish.yml
vendored
23
.github/workflows/publish.yml
vendored
@ -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
3
.gitignore
vendored
@ -18,4 +18,5 @@ out/
|
|||||||
|
|
||||||
!/.idea/copyright/
|
!/.idea/copyright/
|
||||||
!/.idea/scopes/
|
!/.idea/scopes/
|
||||||
/kotlin-js-store/yarn.lock
|
/gradle/yarn.lock
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
with(languageSettings) {
|
jvmToolchain(11)
|
||||||
optIn("kotlin.contracts.ExperimentalContracts")
|
sourceSets.all {
|
||||||
optIn("kotlin.ExperimentalUnsignedTypes")
|
with(languageSettings) {
|
||||||
optIn("space.kscience.kmath.misc.UnstableKMathAPI")
|
optIn("kotlin.contracts.ExperimentalContracts")
|
||||||
|
optIn("kotlin.ExperimentalUnsignedTypes")
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
)
|
@ -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
|
||||||
|
@ -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?
|
||||||
|
@ -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 }
|
@ -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]
|
||||||
// */
|
// */
|
||||||
|
@ -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)
|
||||||
|
@ -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) }
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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>>
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,23 +57,38 @@ 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
|
)
|
||||||
)
|
assertVectorEquals(
|
||||||
assertVectorEquals(
|
vector(8.0, -3.0, 3.001),
|
||||||
vector(8.0, -3.0, 3.001),
|
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
|
@Test
|
||||||
fun multiply() {
|
fun multiply() = with(Euclidean3DSpace) {
|
||||||
with(Euclidean3DSpace) {
|
assertVectorEquals(vector(2.0, -4.0, 0.0), vector(1.0, -2.0, 0.0) * 2)
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
}
|
@ -6,6 +6,7 @@ import space.kscience.kmath.operations.RingOps
|
|||||||
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.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
|
||||||
|
|
||||||
@ -23,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>
|
||||||
|
|
||||||
@ -33,8 +36,6 @@ 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
|
||||||
*/
|
*/
|
||||||
@ -55,59 +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)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -115,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,22 +151,22 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,11 +177,11 @@ public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
@ -174,6 +191,8 @@ public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
|
|||||||
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 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> {
|
||||||
|
@ -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)
|
@ -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)
|
|
@ -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"))
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
# kmath-trajectory
|
|
||||||
|
|
||||||
|
|
||||||
${features}
|
|
||||||
|
|
||||||
${artifact}
|
|
||||||
|
|
||||||
## Author
|
|
||||||
Erik Schouten
|
|
||||||
|
|
||||||
Github: ESchouten
|
|
||||||
|
|
||||||
Email: erik-schouten@hotmail.nl
|
|
@ -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))
|
|
||||||
|
|
@ -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)
|
|
@ -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())
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -44,7 +44,6 @@ include(
|
|||||||
":kmath-jupyter",
|
":kmath-jupyter",
|
||||||
":kmath-symja",
|
":kmath-symja",
|
||||||
":kmath-jafama",
|
":kmath-jafama",
|
||||||
":kmath-trajectory",
|
|
||||||
":examples",
|
":examples",
|
||||||
":benchmarks",
|
":benchmarks",
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user