Skip to content

Commit

Permalink
Rebuilt metric space interface to take lists of DoubleArrays instead.…
Browse files Browse the repository at this point in the history
… Leaks less of the internals.
  • Loading branch information
Mikael Vejdemo-Johansson committed Feb 13, 2024
1 parent 92182be commit 60c7cac
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package org.appliedtopology.tda4j

import space.kscience.kmath.tensors.core.DoubleTensor2D
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra
import space.kscience.kmath.linear.Matrix
import space.kscience.kmath.linear.asMatrix
import space.kscience.kmath.linear.linearSpace
import space.kscience.kmath.linear.transpose
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.nd.as2D
import space.kscience.kmath.operations.algebra
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.math.pow

interface FiniteMetricSpace<VertexT> {
Expand Down Expand Up @@ -47,19 +54,26 @@ class ExplicitMetricSpace<VertexT>(val distances: Map<Pair<VertexT, VertexT>, Do
override fun contains(x: VertexT): Boolean = elements.contains(x)
}

class EuclideanMetricSpace(val points: DoubleTensor2D) : FiniteMetricSpace<Int> {
class EuclideanMetricSpace(val points: List<DoubleArray>) : FiniteMetricSpace<Int> {
val pointsND: Matrix<Double> =
with(Double.algebra.linearSpace) {
StructureND.auto(points.size, points[0].size) { (i, j) ->
points[i][j]
}
}.as2D()

override fun distance(
x: Int,
y: Int,
): Double {
@Suppress("ktlint:standard:property-naming")
with(DoubleTensorAlgebra) {
val x_y = points.rowsByIndices(intArrayOf(x)) - points.rowsByIndices(intArrayOf(y))
return x_y.dot(x_y.transposed())[intArrayOf(0, 0)].pow(0.5)
with(Double.algebra.linearSpace) {
val x_y: Matrix<Double> = (pointsND.rows[x] - pointsND.rows[y]).asMatrix()
return x_y.dot(x_y.transpose())[0, 0].pow(0.5)
}
}

override val size: Int = points.shape[0]
override val size: Int = pointsND.shape[0]

override val elements: Iterable<Int>
get() = IntRange(0, size - 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,22 @@ import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.checkAll
import space.kscience.kmath.nd.ShapeND
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.tensors.core.*

fun pointCloudArb(
dimensions: Collection<Int> = (2..10).toList(),
sizes: Collection<Int> = listOf(5, 10, 25, 50, 100, 500),
): Arb<DoubleTensor2D> =
): Arb<List<DoubleArray>> =
arbitrary {
val dim = Arb.element(dimensions).bind()
val size = Arb.element(sizes).bind()
val doubles =
Arb.doubleArray(
Arb.of(listOf(dim * size)),
Arb.double(-100.0, 100.0),
).bind()
Double.tensorAlgebra.withBroadcast {
fromArray(ShapeND(size, dim), doubles).asDoubleTensor2D()
}
(1..size).map {
Arb.doubleArray(
Arb.of(listOf(dim * size)),
Arb.double(-100.0, 100.0),
).bind()
}
doubles
}

class FiniteMetricSpaceSpec : StringSpec({
Expand All @@ -33,9 +30,7 @@ class FiniteMetricSpaceSpec : StringSpec({
mapOf(Pair(0, 0) to 0.0, Pair(0, 1) to 1.0, Pair(1, 0) to 1.0, Pair(1, 1) to 0.0),
)
val msE: FiniteMetricSpace<Int> =
EuclideanMetricSpace(
DoubleTensor2D(2, 2, OffsetDoubleBuffer(DoubleBuffer(0.0, 1.0, 1.0, 0.0), 0, 4)),
)
EuclideanMetricSpace(listOf(doubleArrayOf(0.0, 1.0), doubleArrayOf(1.0, 0.0)))

"An explicit metric space is non-empty" {
(msX.size) shouldBeGreaterThan 0
Expand All @@ -52,15 +47,15 @@ class FiniteMetricSpaceSpec : StringSpec({
}

"Generate random Euclidean metric space" {
checkAll(pointCloudArb()) {
it.colNum shouldBeGreaterThan 0
it.rowNum shouldBeGreaterThan 0
collect("${it.colNum} columns")
collect("${it.rowNum} rows")
Double.tensorAlgebra.withBroadcast {
val diff = it.rowsByIndices(intArrayOf(1)) - it.rowsByIndices(intArrayOf(2))
collect(kotlin.math.floor(kotlin.math.sqrt(diff.dot(diff.transposed())[intArrayOf(0, 0)]) / 10.0))
}
checkAll(100, pointCloudArb()) {
val space = EuclideanMetricSpace(it)
space.pointsND

space.pointsND.colNum shouldBeGreaterThan 0
space.pointsND.rowNum shouldBeGreaterThan 0
collect("${space.pointsND.colNum} columns")
collect("${space.pointsND.rowNum} rows")
collect(space.distance(1, 2))
}
}
})

0 comments on commit 60c7cac

Please sign in to comment.