Merge pull request #124 from knok16/experiment_with_geometry_package

Initial geometry projections implemetations
This commit is contained in:
Alexander Nozik 2021-08-18 09:40:40 +03:00 committed by GitHub
commit bcc666d19e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 426 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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