Merge pull request #124 from knok16/experiment_with_geometry_package
Initial geometry projections implemetations
This commit is contained in:
commit
bcc666d19e
@ -12,14 +12,14 @@ import space.kscience.kmath.operations.invoke
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@OptIn(UnstableKMathAPI::class)
|
||||
public interface Vector2D : Point<Double>, Vector{
|
||||
public interface Vector2D : Point<Double>, Vector {
|
||||
public val x: Double
|
||||
public val y: Double
|
||||
override val size: Int get() = 2
|
||||
|
||||
override operator fun get(index: Int): Double = when (index) {
|
||||
1 -> x
|
||||
2 -> y
|
||||
0 -> x
|
||||
1 -> y
|
||||
else -> error("Accessing outside of point bounds")
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ public interface Vector2D : Point<Double>, Vector{
|
||||
}
|
||||
|
||||
public val Vector2D.r: Double
|
||||
get() = Euclidean2DSpace { sqrt(norm()) }
|
||||
get() = Euclidean2DSpace { norm() }
|
||||
|
||||
@Suppress("FunctionName")
|
||||
public fun Vector2D(x: Double, y: Double): Vector2D = Vector2DImpl(x, y)
|
||||
|
@ -19,9 +19,9 @@ public interface Vector3D : Point<Double>, Vector {
|
||||
override val size: Int get() = 3
|
||||
|
||||
override operator fun get(index: Int): Double = when (index) {
|
||||
1 -> x
|
||||
2 -> y
|
||||
3 -> z
|
||||
0 -> x
|
||||
1 -> y
|
||||
2 -> z
|
||||
else -> error("Accessing outside of point bounds")
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ public interface Vector3D : Point<Double>, Vector {
|
||||
@Suppress("FunctionName")
|
||||
public fun Vector3D(x: Double, y: Double, z: Double): Vector3D = Vector3DImpl(x, y, z)
|
||||
|
||||
public val Vector3D.r: Double get() = Euclidean3DSpace { sqrt(norm()) }
|
||||
public val Vector3D.r: Double get() = Euclidean3DSpace { norm() }
|
||||
|
||||
private data class Vector3DImpl(
|
||||
override val x: Double,
|
||||
|
@ -0,0 +1,20 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
/**
|
||||
* Project vector onto a line.
|
||||
* @param vector to project
|
||||
* @param line line to which vector should be projected
|
||||
*/
|
||||
public fun <V : Vector> GeometrySpace<V>.projectToLine(vector: V, line: Line<V>): V = with(line) {
|
||||
base + (direction dot (vector - base)) / (direction dot direction) * direction
|
||||
}
|
||||
|
||||
/**
|
||||
* Project vector onto a hyperplane, which is defined by a normal and base.
|
||||
* In 2D case it is the projection to a line, in 3d case it is the one to a plane.
|
||||
* @param vector to project
|
||||
* @param normal normal (perpendicular) vector to a hyper-plane to which vector should be projected
|
||||
* @param base point belonging to a hyper-plane to which vector should be projected
|
||||
*/
|
||||
public fun <V : Vector> GeometrySpace<V>.projectAlong(vector: V, normal: V, base: V): V =
|
||||
vector + normal * ((base - vector) dot normal) / (normal dot normal)
|
@ -0,0 +1,62 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class Euclidean2DSpaceTest {
|
||||
@Test
|
||||
fun zero() {
|
||||
assertVectorEquals(Vector2D(0.0, 0.0), Euclidean2DSpace.zero)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun norm() {
|
||||
with(Euclidean2DSpace) {
|
||||
assertEquals(0.0, zero.norm())
|
||||
assertEquals(1.0, Vector2D(1.0, 0.0).norm())
|
||||
assertEquals(sqrt(2.0), Vector2D(1.0, 1.0).norm())
|
||||
assertEquals(sqrt(5.002001), Vector2D(-2.0, 1.001).norm())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dotProduct() {
|
||||
with(Euclidean2DSpace) {
|
||||
assertEquals(0.0, zero dot zero)
|
||||
assertEquals(0.0, zero dot Vector2D(1.0, 0.0))
|
||||
assertEquals(0.0, Vector2D(-2.0, 0.001) dot zero)
|
||||
assertEquals(0.0, Vector2D(1.0, 0.0) dot Vector2D(0.0, 1.0))
|
||||
|
||||
assertEquals(1.0, Vector2D(1.0, 0.0) dot Vector2D(1.0, 0.0))
|
||||
assertEquals(-2.0, Vector2D(0.0, 1.0) dot Vector2D(1.0, -2.0))
|
||||
assertEquals(2.0, Vector2D(1.0, 1.0) dot Vector2D(1.0, 1.0))
|
||||
assertEquals(4.001001, Vector2D(-2.0, 1.001) dot Vector2D(-2.0, 0.001))
|
||||
|
||||
assertEquals(-4.998, Vector2D(1.0, 2.0) dot Vector2D(-5.0, 0.001))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun add() {
|
||||
with(Euclidean2DSpace) {
|
||||
assertVectorEquals(
|
||||
Vector2D(-2.0, 0.001),
|
||||
Vector2D(-2.0, 0.001) + zero
|
||||
)
|
||||
assertVectorEquals(
|
||||
Vector2D(-3.0, 3.001),
|
||||
Vector2D(2.0, 3.0) + Vector2D(-5.0, 0.001)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiply() {
|
||||
with(Euclidean2DSpace) {
|
||||
assertVectorEquals(Vector2D(-4.0, 0.0), Vector2D(-2.0, 0.0) * 2)
|
||||
assertVectorEquals(Vector2D(4.0, 0.0), Vector2D(-2.0, 0.0) * -2)
|
||||
assertVectorEquals(Vector2D(300.0, 0.0003), Vector2D(100.0, 0.0001) * 3)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class Euclidean3DSpaceTest {
|
||||
@Test
|
||||
fun zero() {
|
||||
assertVectorEquals(Vector3D(0.0, 0.0, 0.0), Euclidean3DSpace.zero)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun distance() {
|
||||
with(Euclidean3DSpace) {
|
||||
assertEquals(0.0, zero.distanceTo(zero))
|
||||
assertEquals(1.0, zero.distanceTo(Vector3D(1.0, 0.0, 0.0)))
|
||||
assertEquals(kotlin.math.sqrt(5.000001), Vector3D(1.0, -2.0, 0.001).distanceTo(zero))
|
||||
assertEquals(0.0, Vector3D(1.0, -2.0, 0.001).distanceTo(Vector3D(1.0, -2.0, 0.001)))
|
||||
assertEquals(0.0, Vector3D(1.0, 0.0, 0.0).distanceTo(Vector3D(1.0, 0.0, 0.0)))
|
||||
assertEquals(kotlin.math.sqrt(2.0), Vector3D(1.0, 0.0, 0.0).distanceTo(Vector3D(1.0, 1.0, 1.0)))
|
||||
assertEquals(3.1622778182822584, Vector3D(0.0, 1.0, 0.0).distanceTo(Vector3D(1.0, -2.0, 0.001)))
|
||||
assertEquals(0.0, Vector3D(1.0, -2.0, 0.001).distanceTo(Vector3D(1.0, -2.0, 0.001)))
|
||||
assertEquals(9.695050335093676, Vector3D(1.0, 2.0, 3.0).distanceTo(Vector3D(7.0, -5.0, 0.001)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun norm() {
|
||||
with(Euclidean3DSpace) {
|
||||
assertEquals(0.0, zero.norm())
|
||||
assertEquals(1.0, Vector3D(1.0, 0.0, 0.0).norm())
|
||||
assertEquals(kotlin.math.sqrt(3.0), Vector3D(1.0, 1.0, 1.0).norm())
|
||||
assertEquals(kotlin.math.sqrt(5.000001), Vector3D(1.0, -2.0, 0.001).norm())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dotProduct() {
|
||||
with(Euclidean3DSpace) {
|
||||
assertEquals(0.0, zero dot zero)
|
||||
assertEquals(0.0, zero dot Vector3D(1.0, 0.0, 0.0))
|
||||
assertEquals(0.0, Vector3D(1.0, -2.0, 0.001) dot zero)
|
||||
|
||||
assertEquals(1.0, Vector3D(1.0, 0.0, 0.0) dot Vector3D(1.0, 0.0, 0.0))
|
||||
assertEquals(1.0, Vector3D(1.0, 0.0, 0.0) dot Vector3D(1.0, 1.0, 1.0))
|
||||
assertEquals(-2.0, Vector3D(0.0, 1.0, 0.0) dot Vector3D(1.0, -2.0, 0.001))
|
||||
assertEquals(3.0, Vector3D(1.0, 1.0, 1.0) dot Vector3D(1.0, 1.0, 1.0))
|
||||
assertEquals(5.000001, Vector3D(1.0, -2.0, 0.001) dot Vector3D(1.0, -2.0, 0.001))
|
||||
|
||||
assertEquals(-2.997, Vector3D(1.0, 2.0, 3.0) dot Vector3D(7.0, -5.0, 0.001))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun add() {
|
||||
with(Euclidean3DSpace) {
|
||||
assertVectorEquals(
|
||||
Vector3D(1.0, -2.0, 0.001),
|
||||
Vector3D(1.0, -2.0, 0.001) + zero
|
||||
)
|
||||
assertVectorEquals(
|
||||
Vector3D(8.0, -3.0, 3.001),
|
||||
Vector3D(1.0, 2.0, 3.0) + Vector3D(7.0, -5.0, 0.001)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiply() {
|
||||
with(Euclidean3DSpace) {
|
||||
assertVectorEquals(Vector3D(2.0, -4.0, 0.0), Vector3D(1.0, -2.0, 0.0) * 2)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class ProjectionAlongTest {
|
||||
@Test
|
||||
fun projectionIntoYEqualsX() {
|
||||
with(Euclidean2DSpace) {
|
||||
val normal = Vector2D(-2.0, 2.0)
|
||||
val base = Vector2D(2.3, 2.3)
|
||||
|
||||
assertVectorEquals(zero, projectAlong(zero, normal, base))
|
||||
|
||||
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
|
||||
val d = (y - x) / 2.0
|
||||
assertVectorEquals(Vector2D(x + d, y - d), projectAlong(Vector2D(x, y), normal, base))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectionOntoLine() {
|
||||
with(Euclidean2DSpace) {
|
||||
val a = 5.0
|
||||
val b = -3.0
|
||||
val c = -15.0
|
||||
val normal = Vector2D(-5.0, 3.0)
|
||||
val base = Vector2D(3.0, 0.0)
|
||||
|
||||
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
|
||||
val xProj = (b * (b * x - a * y) - a * c) / (a * a + b * b)
|
||||
val yProj = (a * (-b * x + a * y) - b * c) / (a * a + b * b)
|
||||
assertVectorEquals(Vector2D(xProj, yProj), projectAlong(Vector2D(x, y), normal, base))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectOntoPlane() {
|
||||
val normal = Vector3D(1.0, 3.5, 0.07)
|
||||
val base = Vector3D(2.0, -0.0037, 11.1111)
|
||||
|
||||
with(Euclidean3DSpace) {
|
||||
val testDomain = (-10.0..10.0).generateList(0.43)
|
||||
for (x in testDomain) {
|
||||
for (y in testDomain) {
|
||||
for (z in testDomain) {
|
||||
val v = Vector3D(x, y, z)
|
||||
val result = projectAlong(v, normal, base)
|
||||
|
||||
// assert that result is on plane
|
||||
assertTrue(isOrthogonal(result - base, normal))
|
||||
// assert that PV vector is collinear to normal vector
|
||||
assertTrue(isCollinear(v - result, normal))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class ProjectionOntoLineTest {
|
||||
@Test
|
||||
fun projectionIntoOx() {
|
||||
with(Euclidean2DSpace) {
|
||||
val ox = Line(zero, Vector2D(1.0, 0.0))
|
||||
|
||||
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
|
||||
assertVectorEquals(Vector2D(x, 0.0), projectToLine(Vector2D(x, y), ox))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectionIntoOy() {
|
||||
with(Euclidean2DSpace) {
|
||||
val line = Line(zero, Vector2D(0.0, 1.0))
|
||||
|
||||
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
|
||||
assertVectorEquals(Vector2D(0.0, y), projectToLine(Vector2D(x, y), line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectionIntoYEqualsX() {
|
||||
with(Euclidean2DSpace) {
|
||||
val line = Line(zero, Vector2D(1.0, 1.0))
|
||||
|
||||
assertVectorEquals(zero, projectToLine(zero, line))
|
||||
|
||||
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
|
||||
val d = (y - x) / 2.0
|
||||
assertVectorEquals(Vector2D(x + d, y - d), projectToLine(Vector2D(x, y), line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectionOntoLine2d() {
|
||||
with(Euclidean2DSpace) {
|
||||
val a = 5.0
|
||||
val b = -3.0
|
||||
val c = -15.0
|
||||
val line = Line(Vector2D(3.0, 0.0), Vector2D(3.0, 5.0))
|
||||
|
||||
grid(-10.0..10.0, -10.0..10.0, 0.15).forEach { (x, y) ->
|
||||
val xProj = (b * (b * x - a * y) - a * c) / (a * a + b * b)
|
||||
val yProj = (a * (-b * x + a * y) - b * c) / (a * a + b * b)
|
||||
assertVectorEquals(Vector2D(xProj, yProj), projectToLine(Vector2D(x, y), line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectionOntoLine3d() {
|
||||
val line = Line3D(
|
||||
base = Vector3D(1.0, 3.5, 0.07),
|
||||
direction = Vector3D(2.0, -0.0037, 11.1111)
|
||||
)
|
||||
|
||||
with(Euclidean3DSpace) {
|
||||
val testDomain = (-10.0..10.0).generateList(0.43)
|
||||
for (x in testDomain) {
|
||||
for (y in testDomain) {
|
||||
for (z in testDomain) {
|
||||
val v = Vector3D(x, y, z)
|
||||
val result = projectToLine(v, line)
|
||||
|
||||
// assert that result is on line
|
||||
assertTrue(isCollinear(result - line.base, line.direction))
|
||||
// assert that PV vector is orthogonal to direction vector
|
||||
assertTrue(isOrthogonal(v - result, line.direction))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import space.kscience.kmath.structures.asSequence
|
||||
import space.kscience.kmath.structures.toList
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.Test
|
||||
|
||||
internal class Vector2DTest {
|
||||
private val vector = Vector2D(1.0, -7.999)
|
||||
|
||||
@Test
|
||||
fun size() {
|
||||
assertEquals(2, vector.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get() {
|
||||
assertEquals(1.0, vector[0])
|
||||
assertEquals(-7.999, vector[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun iterator() {
|
||||
assertEquals(listOf(1.0, -7.999), vector.toList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun x() {
|
||||
assertEquals(1.0, vector.x)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun y() {
|
||||
assertEquals(-7.999, vector.y)
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import space.kscience.kmath.structures.toList
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class Vector3DTest {
|
||||
private val vector = Vector3D(1.0, -7.999, 0.001)
|
||||
|
||||
@Test
|
||||
fun size() {
|
||||
assertEquals(3, vector.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get() {
|
||||
assertEquals(1.0, vector[0])
|
||||
assertEquals(-7.999, vector[1])
|
||||
assertEquals(0.001, vector[2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun iterator() {
|
||||
assertEquals(listOf(1.0, -7.999, 0.001), vector.toList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun x() {
|
||||
assertEquals(1.0, vector.x)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun y() {
|
||||
assertEquals(-7.999, vector.y)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun z() {
|
||||
assertEquals(0.001, vector.z)
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package space.kscience.kmath.geometry
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
fun ClosedRange<Double>.generateList(step: Double): List<Double> = generateSequence(start) { previous ->
|
||||
if (previous == Double.POSITIVE_INFINITY) return@generateSequence null
|
||||
val next = previous + step
|
||||
if (next > endInclusive) null else next
|
||||
}.toList()
|
||||
|
||||
fun grid(
|
||||
xRange: ClosedRange<Double>,
|
||||
yRange: ClosedRange<Double>,
|
||||
step: Double
|
||||
): List<Pair<Double, Double>> {
|
||||
val xs = xRange.generateList(step)
|
||||
val ys = yRange.generateList(step)
|
||||
|
||||
return xs.flatMap { x -> ys.map { y -> x to y } }
|
||||
}
|
||||
|
||||
fun assertVectorEquals(expected: Vector2D, actual: Vector2D, absoluteTolerance: Double = 1e-6) {
|
||||
assertEquals(expected.x, actual.x, absoluteTolerance)
|
||||
assertEquals(expected.y, actual.y, absoluteTolerance)
|
||||
}
|
||||
|
||||
fun assertVectorEquals(expected: Vector3D, actual: Vector3D, absoluteTolerance: Double = 1e-6) {
|
||||
assertEquals(expected.x, actual.x, absoluteTolerance)
|
||||
assertEquals(expected.y, actual.y, absoluteTolerance)
|
||||
assertEquals(expected.z, actual.z, absoluteTolerance)
|
||||
}
|
||||
|
||||
fun <V : Vector> GeometrySpace<V>.isCollinear(a: V, b: V, absoluteTolerance: Double = 1e-6): Boolean {
|
||||
val aDist = a.distanceTo(zero)
|
||||
val bDist = b.distanceTo(zero)
|
||||
return abs(aDist) < absoluteTolerance || abs(bDist) < absoluteTolerance || abs(abs((a dot b) / (aDist * bDist)) - 1) < absoluteTolerance
|
||||
}
|
||||
|
||||
fun <V : Vector> GeometrySpace<V>.isOrthogonal(a: V, b: V, absoluteTolerance: Double = 1e-6): Boolean =
|
||||
abs(a dot b) < absoluteTolerance
|
Loading…
Reference in New Issue
Block a user