Refactor geometry

This commit is contained in:
Alexander Nozik 2023-08-12 13:16:18 +03:00
parent 19bebfd1ed
commit efb853c1bc
28 changed files with 454 additions and 154 deletions

View File

@ -3,11 +3,14 @@
## Unreleased
### Added
- Integer divistion algebras
- Integer division algebras
- Float32 geometries
### Changed
- Default naming for algebra and buffers now uses IntXX/FloatXX notation instead of Java types.
- Remove unnecessary inlines in basic algebras.
- QuaternionField -> QuaternionAlgebra and does not implement `Field` anymore since it is non-commutative
- kmath-geometry is split into `euclidean2d` and `euclidean3d`
### Deprecated

View File

@ -122,16 +122,17 @@ public val Quaternion.reciprocal: Quaternion
/**
* Produce a normalized version of this quaternion
*/
public fun Quaternion.normalized(): Quaternion = with(QuaternionField){ this@normalized / norm(this@normalized) }
public fun Quaternion.normalized(): Quaternion = with(QuaternionAlgebra){ this@normalized / norm(this@normalized) }
/**
* A field of [Quaternion].
*/
@OptIn(UnstableKMathAPI::class)
public object QuaternionField : Field<Quaternion>, Norm<Quaternion, Double>, PowerOperations<Quaternion>,
public object QuaternionAlgebra : Group<Quaternion>, Norm<Quaternion, Double>, PowerOperations<Quaternion>,
ExponentialOperations<Quaternion>, NumbersAddOps<Quaternion>, ScaleOperations<Quaternion> {
override val zero: Quaternion = Quaternion(0.0)
override val one: Quaternion = Quaternion(1.0)
public val one: Quaternion = Quaternion(1.0)
/**
* The `i` quaternion unit.
@ -154,14 +155,16 @@ public object QuaternionField : Field<Quaternion>, Norm<Quaternion, Double>, Pow
override fun scale(a: Quaternion, value: Double): Quaternion =
Quaternion(a.w * value, a.x * value, a.y * value, a.z * value)
override fun multiply(left: Quaternion, right: Quaternion): Quaternion = Quaternion(
public fun multiply(left: Quaternion, right: Quaternion): Quaternion = Quaternion(
left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z,
left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y,
left.w * right.y - left.x * right.z + left.y * right.w + left.z * right.x,
left.w * right.z + left.x * right.y - left.y * right.x + left.z * right.w,
)
override fun divide(left: Quaternion, right: Quaternion): Quaternion {
public operator fun Quaternion.times(other: Quaternion): Quaternion = multiply(this, other)
public fun divide(left: Quaternion, right: Quaternion): Quaternion {
val s = right.w * right.w + right.x * right.x + right.y * right.y + right.z * right.z
return Quaternion(
@ -172,14 +175,17 @@ public object QuaternionField : Field<Quaternion>, Norm<Quaternion, Double>, Pow
)
}
public operator fun Quaternion.div(other: Quaternion): Quaternion = divide(this, other)
override fun power(arg: Quaternion, pow: Number): Quaternion {
if (pow is Int) return pwr(arg, pow)
if (floor(pow.toDouble()) == pow.toDouble()) return pwr(arg, pow.toInt())
if (pow is Int) return power(arg, pow)
if (floor(pow.toDouble()) == pow.toDouble()) return power(arg, pow.toInt())
return exp(pow * ln(arg))
}
private fun pwr(x: Quaternion, a: Int): Quaternion = when {
a < 0 -> -(pwr(x, -a))
private fun power(x: Quaternion, a: Int): Quaternion = when {
a < 0 -> -(power(x, -a))
a == 0 -> one
a == 1 -> x
a == 2 -> pwr2(x)
@ -243,14 +249,15 @@ public object QuaternionField : Field<Quaternion>, Norm<Quaternion, Double>, Pow
return Quaternion(ln(n), th * arg.x, th * arg.y, th * arg.z)
}
override operator fun Number.plus(other: Quaternion): Quaternion =
public override operator fun Number.plus(other: Quaternion): Quaternion =
Quaternion(toDouble() + other.w, other.x, other.y, other.z)
override operator fun Number.minus(other: Quaternion): Quaternion =
public override operator fun Number.minus(other: Quaternion): Quaternion =
Quaternion(toDouble() - other.w, -other.x, -other.y, -other.z)
override operator fun Quaternion.plus(other: Number): Quaternion = Quaternion(w + other.toDouble(), x, y, z)
override operator fun Quaternion.minus(other: Number): Quaternion = Quaternion(w - other.toDouble(), x, y, z)
public override operator fun Quaternion.plus(other: Number): Quaternion = Quaternion(w + other.toDouble(), x, y, z)
public override operator fun Quaternion.minus(other: Number): Quaternion = Quaternion(w - other.toDouble(), x, y, z)
override operator fun Number.times(arg: Quaternion): Quaternion =
Quaternion(toDouble() * arg.w, toDouble() * arg.x, toDouble() * arg.y, toDouble() * arg.z)
@ -275,7 +282,7 @@ public object QuaternionField : Field<Quaternion>, Norm<Quaternion, Double>, Pow
override fun sinh(arg: Quaternion): Quaternion = (exp(arg) - exp(-arg)) / 2.0
override fun cosh(arg: Quaternion): Quaternion = (exp(arg) + exp(-arg)) / 2.0
override fun tanh(arg: Quaternion): Quaternion = (exp(arg) - exp(-arg)) / (exp(-arg) + exp(arg))
override fun asinh(arg: Quaternion): Quaternion = ln(sqrt(arg * arg + one) + arg)
override fun asinh(arg: Quaternion): Quaternion = ln(sqrt(pwr2(arg) + one) + arg)
override fun acosh(arg: Quaternion): Quaternion = ln(arg + sqrt((arg - one) * (arg + one)))
override fun atanh(arg: Quaternion): Quaternion = (ln(arg + one) - ln(one - arg)) / 2.0
}

View File

@ -14,20 +14,20 @@ internal class QuaternionTest {
@Test
fun testNorm() {
assertEquals(2.0, QuaternionField.norm(Quaternion(1.0, 1.0, 1.0, 1.0)))
assertEquals(2.0, QuaternionAlgebra.norm(Quaternion(1.0, 1.0, 1.0, 1.0)))
}
@Test
fun testInverse() = QuaternionField {
fun testInverse() = QuaternionAlgebra {
val q = Quaternion(1.0, 2.0, -3.0, 4.0)
assertBufferEquals(one, q * q.reciprocal, 1e-4)
}
@Test
fun testAddition() {
assertEquals(Quaternion(42, 42), QuaternionField { Quaternion(16, 16) + Quaternion(26, 26) })
assertEquals(Quaternion(42, 16), QuaternionField { Quaternion(16, 16) + 26 })
assertEquals(Quaternion(42, 16), QuaternionField { 26 + Quaternion(16, 16) })
assertEquals(Quaternion(42, 42), QuaternionAlgebra { Quaternion(16, 16) + Quaternion(26, 26) })
assertEquals(Quaternion(42, 16), QuaternionAlgebra { Quaternion(16, 16) + 26 })
assertEquals(Quaternion(42, 16), QuaternionAlgebra { 26 + Quaternion(16, 16) })
}
// @Test
@ -39,9 +39,9 @@ internal class QuaternionTest {
@Test
fun testMultiplication() {
assertEquals(Quaternion(42, 42), QuaternionField { Quaternion(4.2, 0) * Quaternion(10, 10) })
assertEquals(Quaternion(42, 21), QuaternionField { Quaternion(4.2, 2.1) * 10 })
assertEquals(Quaternion(42, 21), QuaternionField { 10 * Quaternion(4.2, 2.1) })
assertEquals(Quaternion(42, 42), QuaternionAlgebra { Quaternion(4.2, 0) * Quaternion(10, 10) })
assertEquals(Quaternion(42, 21), QuaternionAlgebra { Quaternion(4.2, 2.1) * 10 })
assertEquals(Quaternion(42, 21), QuaternionAlgebra { 10 * Quaternion(4.2, 2.1) })
}
// @Test
@ -53,11 +53,11 @@ internal class QuaternionTest {
@Test
fun testPower() {
assertEquals(QuaternionField.zero, QuaternionField { zero pow 2 })
assertEquals(QuaternionField.zero, QuaternionField { zero pow 2 })
assertEquals(QuaternionAlgebra.zero, QuaternionAlgebra { zero pow 2 })
assertEquals(QuaternionAlgebra.zero, QuaternionAlgebra { zero pow 2 })
assertEquals(
QuaternionField { i * 8 }.let { it.x.toInt() to it.w.toInt() },
QuaternionField { Quaternion(2, 2) pow 2 }.let { it.x.toInt() to it.w.toInt() })
QuaternionAlgebra { i * 8 }.let { it.x.toInt() to it.w.toInt() },
QuaternionAlgebra { Quaternion(2, 2) pow 2 }.let { it.x.toInt() to it.w.toInt() })
}
}

View File

@ -300,7 +300,7 @@ public interface Ring<T> : Group<T>, RingOps<T> {
* commutative operations [add] and [multiply]; binary operation [divide] as multiplication of left operand by
* reciprocal of right one.
*
* @param T the type of element of this semifield.
* @param T the type of the semifield element.
*/
public interface FieldOps<T> : RingOps<T> {
/**
@ -336,10 +336,12 @@ public interface FieldOps<T> : RingOps<T> {
/**
* Represents field i.e., algebraic structure with three operations: associative, commutative addition and
* multiplication, and division. **This interface differs from the eponymous mathematical definition: fields in KMath
* multiplication, and division.
*
* **This interface differs from the eponymous mathematical definition: fields in KMath
* also support associative multiplication by scalar.**
*
* @param T the type of element of this field.
* @param T the type of the field element.
*/
public interface Field<T> : Ring<T>, FieldOps<T>, ScaleOperations<T>, NumericAlgebra<T> {
override fun number(value: Number): T = scale(one, value.toDouble())

View File

@ -150,7 +150,7 @@ public interface ScaleOperations<T> : Algebra<T> {
* TODO to be removed and replaced by extensions after multiple receivers are there
*/
@UnstableKMathAPI
public interface NumbersAddOps<T> : RingOps<T>, NumericAlgebra<T> {
public interface NumbersAddOps<T> : GroupOps<T>, NumericAlgebra<T> {
/**
* Addition of element and scalar.
*
@ -177,7 +177,7 @@ public interface NumbersAddOps<T> : RingOps<T>, NumericAlgebra<T> {
public operator fun T.minus(other: Number): T = this - number(other)
/**
* Subtraction of number from element.
* Subtraction of number from the element.
*
* @receiver the minuend.
* @param other the subtrahend.

View File

@ -82,9 +82,9 @@ public expect fun Number.isInteger(): Boolean
/**
* A context extension to include power operations based on exponentiation.
*
* @param T the type of element of this structure.
* @param T the type of this structure element
*/
public interface PowerOperations<T> : FieldOps<T> {
public interface PowerOperations<T>: Algebra<T> {
/**
* Raises [arg] to a power if possible (negative number could not be raised to a fractional power).

View File

@ -16,7 +16,6 @@ import kotlin.math.pow
public typealias DoubleVector = Point<Double>
@Suppress("FunctionName")
public fun DoubleVector(vararg doubles: Double): DoubleVector = doubles.asBuffer()
/**

View File

@ -7,6 +7,8 @@ package space.kscience.kmath.geometry
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.euclidean2d.DoubleVector2D
import space.kscience.kmath.geometry.euclidean3d.DoubleVector3D
/**
* A line formed by [start] vector of start and a [direction] vector. Direction vector is not necessarily normalized,

View File

@ -0,0 +1,29 @@
/*
* 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
import space.kscience.kmath.linear.Point
public interface Vector2D<T> : Point<T>, Vector {
public val x: T
public val y: T
override val size: Int get() = 2
override operator fun get(index: Int): T = when (index) {
0 -> x
1 -> y
else -> error("Accessing outside of point bounds")
}
override operator fun iterator(): Iterator<T> = iterator {
yield(x)
yield(y)
}
}
public operator fun <T> Vector2D<T>.component1(): T = x
public operator fun <T> Vector2D<T>.component2(): T = y

View File

@ -0,0 +1,41 @@
/*
* 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
import space.kscience.kmath.linear.Point
import space.kscience.kmath.structures.Buffer
public interface Vector3D<T> : Point<T>, Vector {
public val x: T
public val y: T
public val z: T
override val size: Int get() = 3
override operator fun get(index: Int): T = when (index) {
0 -> x
1 -> y
2 -> z
else -> error("Accessing outside of point bounds")
}
override operator fun iterator(): Iterator<T> = listOf(x, y, z).iterator()
}
public operator fun <T> Vector3D<T>.component1(): T = x
public operator fun <T> Vector3D<T>.component2(): T = y
public operator fun <T> Vector3D<T>.component3(): T = z
public fun <T> Buffer<T>.asVector3D(): Vector3D<T> = object : Vector3D<T> {
init {
require(this@asVector3D.size == 3) { "Buffer of size 3 is required for Vector3D" }
}
override val x: T get() = this@asVector3D[0]
override val y: T get() = this@asVector3D[1]
override val z: T get() = this@asVector3D[2]
override fun toString(): String = this@asVector3D.toString()
}

View File

@ -1,20 +1,19 @@
/*
* Copyright 2018-2022 KMath contributors.
* 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
package space.kscience.kmath.geometry.euclidean2d
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo
import kotlin.math.*
import kotlin.math.PI
/**
* A circle in 2D space
*/
@Serializable
public data class Circle2D(
@Serializable(Euclidean2DSpace.VectorSerializer::class) public val center: DoubleVector2D,
@Serializable(Float64Space2D.VectorSerializer::class) public val center: DoubleVector2D,
public val radius: Double
)

View File

@ -0,0 +1,79 @@
/*
* 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.euclidean2d
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector2D
import space.kscience.kmath.operations.Float32Field
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow
import kotlin.math.sqrt
@Serializable(Float32Space2D.VectorSerializer::class)
public interface Float32Vector2D: Vector2D<Float>
public object Float32Space2D :
GeometrySpace<Float32Vector2D>,
ScaleOperations<Float32Vector2D> {
@Serializable
@SerialName("Float32Vector2D")
private data class Vector2DImpl(
override val x: Float,
override val y: Float,
) : Float32Vector2D
public object VectorSerializer : KSerializer<Float32Vector2D> {
private val proxySerializer = Vector2DImpl.serializer()
override val descriptor: SerialDescriptor get() = proxySerializer.descriptor
override fun deserialize(decoder: Decoder): Float32Vector2D = decoder.decodeSerializableValue(proxySerializer)
override fun serialize(encoder: Encoder, value: Float32Vector2D) {
val vector = value as? Vector2DImpl ?: Vector2DImpl(value.x, value.y)
encoder.encodeSerializableValue(proxySerializer, vector)
}
}
public fun vector(x: Float, y: Float): Float32Vector2D =
Vector2DImpl(x, y)
public fun vector(x: Number, y: Number): Float32Vector2D =
vector(x.toFloat(), y.toFloat())
override val zero: Float32Vector2D by lazy { vector(0f, 0f) }
override fun norm(arg: Float32Vector2D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2)).toDouble()
public fun Float32Vector2D.norm(): Double = norm(this)
override fun Float32Vector2D.unaryMinus(): Float32Vector2D = vector(-x, -y)
override fun Float32Vector2D.distanceTo(other: Float32Vector2D): Double = (this - other).norm()
override fun add(left: Float32Vector2D, right: Float32Vector2D): Float32Vector2D =
vector(left.x + right.x, left.y + right.y)
override fun scale(a: Float32Vector2D, value: Double): Float32Vector2D =
vector(a.x * value, a.y * value)
override fun Float32Vector2D.dot(other: Float32Vector2D): Double =
(x * other.x + y * other.y).toDouble()
public val xAxis: Float32Vector2D = vector(1.0, 0.0)
public val yAxis: Float32Vector2D = vector(0.0, 1.0)
}
public fun Float32Vector2D(x: Number, y: Number): Float32Vector2D = Float32Space2D.vector(x, y)
public val Float32Field.euclidean2D: Float32Space2D get() = Float32Space2D

View File

@ -1,9 +1,9 @@
/*
* Copyright 2018-2022 KMath contributors.
* 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
package space.kscience.kmath.geometry.euclidean2d
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
@ -11,43 +11,26 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.linear.Point
import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector2D
import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.operations.Norm
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow
import kotlin.math.sqrt
public interface Vector2D<T> : Point<T>, Vector {
public val x: T
public val y: T
override val size: Int get() = 2
override operator fun get(index: Int): T = when (index) {
0 -> x
1 -> y
else -> error("Accessing outside of point bounds")
}
override operator fun iterator(): Iterator<T> = iterator {
yield(x)
yield(y)
}
}
public operator fun <T> Vector2D<T>.component1(): T = x
public operator fun <T> Vector2D<T>.component2(): T = y
public typealias DoubleVector2D = Vector2D<Double>
public typealias Float64Vector2D = Vector2D<Double>
public val Vector2D<Double>.r: Double get() = Euclidean2DSpace.norm(this)
public val Vector2D<Double>.r: Double get() = Float64Space2D.norm(this)
/**
* 2D Euclidean space
*/
public object Euclidean2DSpace : GeometrySpace<DoubleVector2D>,
public object Float64Space2D : GeometrySpace<DoubleVector2D>,
ScaleOperations<DoubleVector2D>,
Norm<DoubleVector2D, Double> {
@ -88,3 +71,5 @@ public object Euclidean2DSpace : GeometrySpace<DoubleVector2D>,
public val xAxis: DoubleVector2D = vector(1.0, 0.0)
public val yAxis: DoubleVector2D = vector(0.0, 1.0)
}
public val Float64Field.euclidean2D: Float64Space2D get() = Float64Space2D

View File

@ -3,7 +3,9 @@
* 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
package space.kscience.kmath.geometry.euclidean2d
import space.kscience.kmath.geometry.Vector2D
/**

View File

@ -0,0 +1,108 @@
/*
* 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.euclidean3d
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector3D
import space.kscience.kmath.operations.Float32Field
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow
import kotlin.math.sqrt
@Serializable(Float32Space3D.VectorSerializer::class)
public interface Float32Vector3D: Vector3D<Float>
public object Float32Space3D :
GeometrySpace<Float32Vector3D>,
ScaleOperations<Float32Vector3D>{
@Serializable
@SerialName("Float32Vector3D")
private data class Vector3DImpl(
override val x: Float,
override val y: Float,
override val z: Float,
) : Float32Vector3D
public object VectorSerializer : KSerializer<Float32Vector3D> {
private val proxySerializer = Vector3DImpl.serializer()
override val descriptor: SerialDescriptor get() = proxySerializer.descriptor
override fun deserialize(decoder: Decoder): Float32Vector3D = decoder.decodeSerializableValue(proxySerializer)
override fun serialize(encoder: Encoder, value: Float32Vector3D) {
val vector = value as? Vector3DImpl ?: Vector3DImpl(value.x, value.y, value.z)
encoder.encodeSerializableValue(proxySerializer, vector)
}
}
public fun vector(x: Float, y: Float, z: Float): Float32Vector3D =
Vector3DImpl(x, y, z)
public fun vector(x: Number, y: Number, z: Number): Float32Vector3D =
vector(x.toFloat(), y.toFloat(), z.toFloat())
override val zero: Float32Vector3D by lazy { vector(0.0, 0.0, 0.0) }
override fun norm(arg: Float32Vector3D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2) + arg.z.pow(2)).toDouble()
public fun Float32Vector3D.norm(): Double = norm(this)
override fun Float32Vector3D.unaryMinus(): Float32Vector3D = vector(-x, -y, -z)
override fun Float32Vector3D.distanceTo(other: Float32Vector3D): Double = (this - other).norm()
override fun add(left: Float32Vector3D, right: Float32Vector3D): Float32Vector3D =
vector(left.x + right.x, left.y + right.y, left.z + right.z)
override fun scale(a: Float32Vector3D, value: Double): Float32Vector3D =
vector(a.x * value, a.y * value, a.z * value)
override fun Float32Vector3D.dot(other: Float32Vector3D): Double =
(x * other.x + y * other.y + z * other.z).toDouble()
/**
* Compute vector product of [first] and [second]. The basis is assumed to be right-handed.
*/
public fun vectorProduct(
first: Float32Vector3D,
second: Float32Vector3D,
): Float32Vector3D {
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 in a right-handed basis
*/
public infix fun Float32Vector3D.cross(other: Float32Vector3D): Float32Vector3D = vectorProduct(this, other)
public val xAxis: Float32Vector3D = vector(1.0, 0.0, 0.0)
public val yAxis: Float32Vector3D = vector(0.0, 1.0, 0.0)
public val zAxis: Float32Vector3D = vector(0.0, 0.0, 1.0)
}
public fun Float32Vector3D(x: Number, y: Number, z: Number): Float32Vector3D = Float32Space3D.vector(x, y, z)
public val Float32Field.euclidean3D: Float32Space3D get() = Float32Space3D

View File

@ -1,9 +1,9 @@
/*
* Copyright 2018-2022 KMath contributors.
* 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
package space.kscience.kmath.geometry.euclidean3d
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
@ -11,51 +11,33 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.linear.Point
import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector3D
import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.operations.Norm
import space.kscience.kmath.operations.ScaleOperations
import space.kscience.kmath.structures.Buffer
import kotlin.math.pow
import kotlin.math.sqrt
public interface Vector3D<T> : Point<T>, Vector {
public val x: T
public val y: T
public val z: T
override val size: Int get() = 3
internal 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
override operator fun get(index: Int): T = when (index) {
0 -> x
1 -> y
2 -> z
else -> error("Accessing outside of point bounds")
}
override operator fun iterator(): Iterator<T> = listOf(x, y, z).iterator()
}
public operator fun <T> Vector3D<T>.component1(): T = x
public operator fun <T> Vector3D<T>.component2(): T = y
public operator fun <T> Vector3D<T>.component3(): T = z
public fun <T> Buffer<T>.asVector3D(): Vector3D<T> = object : Vector3D<T> {
init {
require(this@asVector3D.size == 3) { "Buffer of size 3 is required for Vector3D" }
}
override val x: T get() = this@asVector3D[0]
override val y: T get() = this@asVector3D[1]
override val z: T get() = this@asVector3D[2]
override fun toString(): String = this@asVector3D.toString()
else -> 0
}
public typealias DoubleVector3D = Vector3D<Double>
public typealias Float64Vector3D = Vector3D<Double>
public val DoubleVector3D.r: Double get() = Euclidean3DSpace.norm(this)
public val DoubleVector3D.r: Double get() = Float64Space3D.norm(this)
public object Euclidean3DSpace : GeometrySpace<DoubleVector3D>, ScaleOperations<DoubleVector3D>,
public object Float64Space3D : GeometrySpace<DoubleVector3D>, ScaleOperations<DoubleVector3D>,
Norm<DoubleVector3D, Double> {
@Serializable
@ -103,21 +85,8 @@ public object Euclidean3DSpace : GeometrySpace<DoubleVector3D>, ScaleOperations<
override fun DoubleVector3D.dot(other: DoubleVector3D): Double =
x * other.x + y * other.y + z * other.z
private fun leviCivita(i: Int, j: Int, k: Int): Int = when {
// even permutation
i == 0 && j == 1 && k == 2 -> 1
i == 1 && j == 2 && k == 0 -> 1
i == 2 && j == 0 && k == 1 -> 1
// odd permutations
i == 2 && j == 1 && k == 0 -> -1
i == 0 && j == 2 && k == 1 -> -1
i == 1 && j == 0 && k == 2 -> -1
else -> 0
}
/**
* Compute vector product of [first] and [second]. The basis assumed to be right-handed.
* Compute vector product of [first] and [second]. The basis is assumed to be right-handed.
*/
public fun vectorProduct(
first: DoubleVector3D,
@ -139,7 +108,7 @@ public object Euclidean3DSpace : GeometrySpace<DoubleVector3D>, ScaleOperations<
}
/**
* Vector product with right basis
* Vector product with the right basis
*/
public infix fun DoubleVector3D.cross(other: DoubleVector3D): Vector3D<Double> = vectorProduct(this, other)
@ -147,3 +116,5 @@ public object Euclidean3DSpace : GeometrySpace<DoubleVector3D>, ScaleOperations<
public val yAxis: DoubleVector3D = vector(0.0, 1.0, 0.0)
public val zAxis: DoubleVector3D = vector(0.0, 0.0, 1.0)
}
public val Float64Field.euclidean3D: Float64Space3D get() = Float64Space3D

View File

@ -1,15 +1,16 @@
/*
* Copyright 2018-2022 KMath contributors.
* 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
package space.kscience.kmath.geometry.euclidean3d
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionField
import space.kscience.kmath.complex.QuaternionAlgebra
import space.kscience.kmath.complex.normalized
import space.kscience.kmath.complex.reciprocal
import space.kscience.kmath.geometry.*
import space.kscience.kmath.linear.LinearSpace
import space.kscience.kmath.linear.Matrix
import space.kscience.kmath.linear.linearSpace
@ -31,7 +32,7 @@ public val Quaternion.theta: Radians get() = (kotlin.math.acos(normalized().w) *
public fun Quaternion.Companion.fromRotation(theta: Angle, vector: DoubleVector3D): Quaternion {
val s = sin(theta / 2)
val c = cos(theta / 2)
val norm = with(Euclidean3DSpace) { vector.norm() }
val norm = with(Float64Space3D) { vector.norm() }
return Quaternion(c, vector.x * s / norm, vector.y * s / norm, vector.z * s / norm)
}
@ -50,9 +51,9 @@ public val Quaternion.vector: DoubleVector3D
}
/**
* Rotate a vector in a [Euclidean3DSpace]
* Rotate a vector in a [Float64Space3D]
*/
public fun Euclidean3DSpace.rotate(vector: DoubleVector3D, q: Quaternion): DoubleVector3D = with(QuaternionField) {
public fun Float64Space3D.rotate(vector: DoubleVector3D, q: Quaternion): DoubleVector3D = with(QuaternionAlgebra) {
val p = vector.toQuaternion()
(q * p * q.reciprocal).vector
}
@ -61,10 +62,10 @@ public fun Euclidean3DSpace.rotate(vector: DoubleVector3D, q: Quaternion): Doubl
* Use a composition of quaternions to create a rotation
*/
@UnstableKMathAPI
public fun Euclidean3DSpace.rotate(vector: DoubleVector3D, composition: QuaternionField.() -> Quaternion): DoubleVector3D =
rotate(vector, QuaternionField.composition())
public fun Float64Space3D.rotate(vector: DoubleVector3D, composition: QuaternionAlgebra.() -> Quaternion): DoubleVector3D =
rotate(vector, QuaternionAlgebra.composition())
public fun Euclidean3DSpace.rotate(vector: DoubleVector3D, matrix: Matrix<Double>): DoubleVector3D {
public fun Float64Space3D.rotate(vector: DoubleVector3D, matrix: Matrix<Double>): DoubleVector3D {
require(matrix.colNum == 3 && matrix.rowNum == 3) { "Square 3x3 rotation matrix is required" }
return with(Float64Field.linearSpace) { matrix.dot(vector).asVector3D() }
}
@ -76,7 +77,7 @@ public fun Euclidean3DSpace.rotate(vector: DoubleVector3D, matrix: Matrix<Double
public fun Quaternion.toRotationMatrix(
linearSpace: LinearSpace<Double, *> = Float64Field.linearSpace,
): Matrix<Double> {
val s = QuaternionField.norm(this).pow(-2)
val s = QuaternionAlgebra.norm(this).pow(-2)
return linearSpace.matrix(3, 3)(
1.0 - 2 * s * (y * y + z * z), 2 * s * (x * y - z * w), 2 * s * (x * z + y * w),
2 * s * (x * y + z * w), 1.0 - 2 * s * (x * x + z * z), 2 * s * (y * z - x * w),

View File

@ -6,6 +6,10 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.GeometrySpace.Companion.DEFAULT_PRECISION
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import space.kscience.kmath.geometry.euclidean2d.Float64Vector2D
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import space.kscience.kmath.geometry.euclidean3d.Float64Vector3D
/**
* Float equality within given [precision]
@ -36,7 +40,7 @@ public fun <V : Vector> V.equalsVector(
public fun Float64Vector2D.equalsVector(
other: Float64Vector2D,
precision: Double = DEFAULT_PRECISION,
): Boolean = equalsVector(Euclidean2DSpace, other, precision)
): Boolean = equalsVector(Float64Space2D, other, precision)
/**
* Vector equality using Euclidian L2 norm and given [precision]
@ -44,7 +48,7 @@ public fun Float64Vector2D.equalsVector(
public fun Float64Vector3D.equalsVector(
other: Float64Vector3D,
precision: Double = DEFAULT_PRECISION,
): Boolean = equalsVector(Euclidean3DSpace, other, precision)
): Boolean = equalsVector(Float64Space3D, other, precision)
/**
* Line equality using [GeometrySpace.norm] provided by the [space] and given [precision]

View File

@ -0,0 +1,55 @@
/*
* 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
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionAlgebra
import space.kscience.kmath.complex.conjugate
import space.kscience.kmath.complex.normalized
import space.kscience.kmath.geometry.euclidean3d.Float32Space3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.theta
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.pow
public operator fun Quaternion.times(other: Quaternion): Quaternion = QuaternionAlgebra.multiply(this, other)
public operator fun Quaternion.div(other: Quaternion): Quaternion = QuaternionAlgebra.divide(this, other)
public fun Quaternion.power(number: Number): Quaternion = QuaternionAlgebra.power(this, number)
/**
* Linear interpolation between [from] and [to] in spherical space
*/
public fun QuaternionAlgebra.slerp(from: Quaternion, to: Quaternion, fraction: Double): Quaternion =
(to / from).pow(fraction) * from
public fun QuaternionAlgebra.angleBetween(q1: Quaternion, q2: Quaternion): Angle = (q1.conjugate * q2).theta
public val Quaternion.inclination: Radians get() = asin(2 * (w * y - x * z)).radians
public val Quaternion.azimuth: Angle get() = atan2(2 * (w * z + x * y), 1 - 2 * (y.pow(2) + z.pow(2))).radians.normalized()
public infix fun Quaternion.dot(other: Quaternion): Double = w * other.w + x * other.x + y * other.y + z * other.z
private fun Quaternion.normalizedToEuler(): Float32Vector3D {
val roll = atan2(2 * y * w + 2 * x * z, 1 - 2 * y * y - 2 * z * z);
val pitch = atan2(2 * x * w - 2 * y * z, 1 - 2 * x * x - 2 * z * z);
val yaw = asin(2 * x * y + 2 * z * w);
return Float32Vector3D(roll, pitch, yaw)
}
/**
* Quaternion to XYZ Cardan angles
*/
public fun Quaternion.toEuler(): Float32Vector3D = if (QuaternionAlgebra.norm(this) == 0.0) {
Float32Space3D.zero
} else {
normalized().normalizedToEuler()
}

View File

@ -5,6 +5,7 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import kotlin.math.sqrt
import kotlin.test.Test
import kotlin.test.assertEquals
@ -12,12 +13,12 @@ import kotlin.test.assertEquals
internal class Euclidean2DSpaceTest {
@Test
fun zero() {
assertVectorEquals(Euclidean2DSpace.vector(0.0, 0.0), Euclidean2DSpace.zero)
assertVectorEquals(Float64Space2D.vector(0.0, 0.0), Float64Space2D.zero)
}
@Test
fun norm() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
assertEquals(0.0, norm(zero))
assertEquals(1.0, norm(vector(1.0, 0.0)))
assertEquals(sqrt(2.0), norm(vector(1.0, 1.0)))
@ -27,7 +28,7 @@ internal class Euclidean2DSpaceTest {
@Test
fun dotProduct() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
assertEquals(0.0, zero dot zero)
assertEquals(0.0, zero dot vector(1.0, 0.0))
assertEquals(0.0, vector(-2.0, 0.001) dot zero)
@ -44,7 +45,7 @@ internal class Euclidean2DSpaceTest {
@Test
fun add() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
assertVectorEquals(
vector(-2.0, 0.001),
vector(-2.0, 0.001) + zero
@ -58,7 +59,7 @@ internal class Euclidean2DSpaceTest {
@Test
fun multiply() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
assertVectorEquals(vector(-4.0, 0.0), vector(-2.0, 0.0) * 2)
assertVectorEquals(vector(4.0, 0.0), vector(-2.0, 0.0) * -2)
assertVectorEquals(vector(300.0, 0.0003), vector(100.0, 0.0001) * 3)

View File

@ -5,18 +5,19 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import kotlin.test.Test
import kotlin.test.assertEquals
internal class Euclidean3DSpaceTest {
@Test
fun zero() {
assertVectorEquals(Euclidean3DSpace.vector(0.0, 0.0, 0.0), Euclidean3DSpace.zero)
assertVectorEquals(Float64Space3D.vector(0.0, 0.0, 0.0), Float64Space3D.zero)
}
@Test
fun distance() {
with(Euclidean3DSpace) {
with(Float64Space3D) {
assertEquals(0.0, zero.distanceTo(zero))
assertEquals(1.0, zero.distanceTo(vector(1.0, 0.0, 0.0)))
assertEquals(kotlin.math.sqrt(5.000001), vector(1.0, -2.0, 0.001).distanceTo(zero))
@ -31,7 +32,7 @@ internal class Euclidean3DSpaceTest {
@Test
fun norm() {
with(Euclidean3DSpace) {
with(Float64Space3D) {
assertEquals(0.0, zero.norm())
assertEquals(1.0, vector(1.0, 0.0, 0.0).norm())
assertEquals(kotlin.math.sqrt(3.0), vector(1.0, 1.0, 1.0).norm())
@ -41,7 +42,7 @@ internal class Euclidean3DSpaceTest {
@Test
fun dotProduct() {
with(Euclidean3DSpace) {
with(Float64Space3D) {
assertEquals(0.0, zero dot zero)
assertEquals(0.0, zero dot vector(1.0, 0.0, 0.0))
assertEquals(0.0, vector(1.0, -2.0, 0.001) dot zero)
@ -57,7 +58,7 @@ internal class Euclidean3DSpaceTest {
}
@Test
fun add() = with(Euclidean3DSpace) {
fun add() = with(Float64Space3D) {
assertVectorEquals(
vector(1.0, -2.0, 0.001),
vector(1.0, -2.0, 0.001) + zero
@ -69,19 +70,19 @@ internal class Euclidean3DSpaceTest {
}
@Test
fun multiply() = with(Euclidean3DSpace) {
fun multiply() = with(Float64Space3D) {
assertVectorEquals(vector(2.0, -4.0, 0.0), vector(1.0, -2.0, 0.0) * 2)
}
@Test
fun vectorProduct() = with(Euclidean3DSpace) {
fun vectorProduct() = with(Float64Space3D) {
assertVectorEquals(zAxis, vectorProduct(xAxis, yAxis))
assertVectorEquals(zAxis, xAxis cross yAxis)
assertVectorEquals(-zAxis, vectorProduct(yAxis, xAxis))
}
@Test
fun doubleVectorProduct() = with(Euclidean3DSpace) {
fun doubleVectorProduct() = with(Float64Space3D) {
val a = vector(1, 2, -3)
val b = vector(-1, 0, 1)
val c = vector(4, 5, 6)

View File

@ -5,13 +5,15 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import kotlin.test.Test
import kotlin.test.assertTrue
internal class ProjectionAlongTest {
@Test
fun projectionIntoYEqualsX() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
val normal = vector(-2.0, 2.0)
val base = vector(2.3, 2.3)
@ -26,7 +28,7 @@ internal class ProjectionAlongTest {
@Test
fun projectionOntoLine() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
val a = 5.0
val b = -3.0
val c = -15.0
@ -42,11 +44,11 @@ internal class ProjectionAlongTest {
}
@Test
fun projectOntoPlane() = with(Euclidean3DSpace){
fun projectOntoPlane() = with(Float64Space3D){
val normal = vector(1.0, 3.5, 0.07)
val base = vector(2.0, -0.0037, 11.1111)
with(Euclidean3DSpace) {
with(Float64Space3D) {
val testDomain = (-10.0..10.0).generateList(0.43)
for (x in testDomain) {
for (y in testDomain) {

View File

@ -5,13 +5,15 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import kotlin.test.Test
import kotlin.test.assertTrue
internal class ProjectionOntoLineTest {
@Test
fun projectionIntoOx() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
val ox = Line(zero, vector(1.0, 0.0))
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
@ -22,7 +24,7 @@ internal class ProjectionOntoLineTest {
@Test
fun projectionIntoOy() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
val line = Line(zero, vector(0.0, 1.0))
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
@ -33,7 +35,7 @@ internal class ProjectionOntoLineTest {
@Test
fun projectionIntoYEqualsX() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
val line = Line(zero, vector(1.0, 1.0))
assertVectorEquals(zero, projectToLine(zero, line))
@ -47,7 +49,7 @@ internal class ProjectionOntoLineTest {
@Test
fun projectionOntoLine2d() {
with(Euclidean2DSpace) {
with(Float64Space2D) {
val a = 5.0
val b = -3.0
val c = -15.0
@ -62,7 +64,7 @@ internal class ProjectionOntoLineTest {
}
@Test
fun projectionOntoLine3d() = with(Euclidean3DSpace) {
fun projectionOntoLine3d() = with(Float64Space3D) {
val line = Line(
base = vector(1.0, 3.5, 0.07),
direction = vector(2.0, -0.0037, 11.1111)

View File

@ -7,6 +7,7 @@ package space.kscience.kmath.geometry
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.normalized
import space.kscience.kmath.geometry.euclidean3d.*
import space.kscience.kmath.structures.Float64Buffer
import space.kscience.kmath.testutils.assertBufferEquals
import kotlin.test.Test
@ -14,7 +15,7 @@ import kotlin.test.Test
class RotationTest {
@Test
fun differentRotations() = with(Euclidean3DSpace) {
fun differentRotations() = with(Float64Space3D) {
val vector = vector(1.0, 1.0, 1.0)
val q = Quaternion(1.0, 2.0, -3.0, 4.0).normalized()
val rotatedByQ = rotate(vector, q)
@ -36,7 +37,7 @@ class RotationTest {
@Test
fun fromRotation() {
val q = Quaternion.fromRotation(0.3.radians, Euclidean3DSpace.vector(1.0, 1.0, 1.0))
val q = Quaternion.fromRotation(0.3.radians, Float64Space3D.vector(1.0, 1.0, 1.0))
assertBufferEquals(Float64Buffer(0.9887711, 0.0862781, 0.0862781, 0.0862781), q)
}

View File

@ -6,12 +6,13 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import space.kscience.kmath.operations.toList
import kotlin.test.Test
import kotlin.test.assertEquals
internal class Vector2DTest {
private val vector = Euclidean2DSpace.vector(1.0, -7.999)
private val vector = Float64Space2D.vector(1.0, -7.999)
@Test
fun size() {

View File

@ -5,12 +5,13 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import space.kscience.kmath.operations.toList
import kotlin.test.Test
import kotlin.test.assertEquals
internal class Vector3DTest {
private val vector = Euclidean3DSpace.vector(1.0, -7.999, 0.001)
private val vector = Float64Space3D.vector(1.0, -7.999, 0.001)
@Test
fun size() {

View File

@ -5,6 +5,8 @@
package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean2d.DoubleVector2D
import space.kscience.kmath.geometry.euclidean3d.DoubleVector3D
import kotlin.math.abs
import kotlin.test.assertEquals

View File

@ -16,6 +16,7 @@ import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.ShapeND
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.FieldOps
import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.operations.PowerOperations
@ -32,7 +33,8 @@ internal fun ShapeND.toLongArray(): LongArray = LongArray(size) { get(it).toLong
public class DoubleTensorFlowAlgebra internal constructor(
graph: Graph,
) : TensorFlowAlgebra<Double, TFloat64, Float64Field>(graph), PowerOperations<StructureND<Double>> {
) : TensorFlowAlgebra<Double, TFloat64,
Float64Field>(graph), FieldOps<StructureND<Double>>, PowerOperations<StructureND<Double>> {
override val elementAlgebra: Float64Field get() = Float64Field