-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Vietoris-Rips implemented and somewhat tested; symmetric Vietoris-Rip…
…s implemented; profiling. TODO: figure out whether/why the Vietoris-Rips streams are incorrectly sorted.
- Loading branch information
Mikael Vejdemo-Johansson
committed
Nov 5, 2023
1 parent
8c27c69
commit af42cec
Showing
9 changed files
with
360 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
src/commonMain/kotlin/org/appliedtopology/tda4j/SymmetryGroup.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package org.appliedtopology.tda4j | ||
|
||
interface SymmetryGroup<GroupT, VertexT : Comparable<VertexT>> { | ||
val elements: Collection<GroupT> | ||
|
||
fun action(g: GroupT): (VertexT) -> VertexT | ||
|
||
fun orbit(vertex: VertexT): Set<VertexT> = elements.map { action(it)(vertex) }.toSet() | ||
|
||
fun orbit(simplex: AbstractSimplex<VertexT>): Set<AbstractSimplex<VertexT>> = | ||
elements.map { g -> abstractSimplexOf(simplex.map { v -> action(g)(v) }) }.toSet() | ||
|
||
fun representative(vertex: VertexT): VertexT = orbit(vertex).min() | ||
|
||
fun representative(simplex: AbstractSimplex<VertexT>): AbstractSimplex<VertexT> = | ||
orbit(simplex).minWith { left, right -> AbstractSimplex.compare(left, right) } | ||
|
||
fun isRepresentative(vertex: VertexT): Boolean = vertex == representative(vertex) | ||
|
||
fun isRepresentative(simplex: AbstractSimplex<VertexT>): Boolean = simplex == representative(simplex) | ||
} | ||
|
||
class HyperCubeSymmetry(val elementCount: Int) : SymmetryGroup<Int, Int> { | ||
override val elements: Collection<Int> = (0..factorial(elementCount) - 1).toList() | ||
|
||
val permutations: List<Map<Int, Int>> = elements.map { | ||
run { | ||
val p = permutation(it) | ||
(0..(1 shl elementCount) - 1).associateWith { bits -> | ||
(0..elementCount - 1) | ||
.map { ((bits and (1 shl it) shr it) shl p[it]) } | ||
.fold(0, { x, y -> x or y }) | ||
} | ||
} | ||
} | ||
val knownRepresentatives: MutableSet<AbstractSimplex<Int>> = HashSet() | ||
|
||
fun permutation(n: Int): List<Int> = buildList(elementCount) { | ||
val source = (0..elementCount - 1).toMutableList() | ||
var pos = n | ||
|
||
while (source.isNotEmpty()) { | ||
val div = pos.floorDiv(factorial(source.size - 1)) | ||
pos = pos.mod(factorial(source.size - 1)) | ||
add(source.removeAt(div)) | ||
} | ||
} | ||
|
||
override fun action(g: Int): (Int) -> Int = { permutations[g][it] ?: it } | ||
|
||
override fun isRepresentative(simplex: AbstractSimplex<Int>): Boolean { | ||
if (simplex in knownRepresentatives) return true | ||
val isR = super.isRepresentative(simplex) | ||
if (isR) knownRepresentatives.add(simplex) | ||
return isR | ||
} | ||
|
||
companion object { | ||
fun factorial(n: Int, accum: Int = 1): Int { | ||
return if (n <= 1) accum else factorial(n - 1, n * accum) | ||
} | ||
} | ||
} | ||
|
||
class ExpandSequence<VertexT : Comparable<VertexT>> ( | ||
val representatives: List<AbstractSimplex<VertexT>>, | ||
val symmetryGroup: SymmetryGroup<*, VertexT> | ||
) : Sequence<AbstractSimplex<VertexT>> { | ||
val orbitSizes: List<Int> = representatives.map { symmetryGroup.orbit(it).size } | ||
val orbitRanges: List<Int> = orbitSizes.scan(0) { x, y -> x + y } | ||
|
||
val size: Int = orbitSizes.sum() | ||
|
||
fun get(index: Int): AbstractSimplex<VertexT> { | ||
val orbitIndex = orbitRanges.indexOfFirst { it >= index } - 1 | ||
return symmetryGroup.orbit(representatives[orbitIndex]).toList()[index - orbitRanges[orbitIndex]] | ||
} | ||
|
||
fun isEmpty(): Boolean = size == 0 | ||
|
||
override fun iterator(): Iterator<AbstractSimplex<VertexT>> = sequence { | ||
representatives.forEach({ t -> yieldAll(symmetryGroup.orbit(t)) }) | ||
}.iterator() | ||
} | ||
|
||
class SymmetricZomorodianIncremental<VertexT : Comparable<VertexT>>(val symmetryGroup: SymmetryGroup<*, VertexT>) : CliqueFinder<VertexT> { | ||
override fun cliques( | ||
metricSpace: FiniteMetricSpace<VertexT>, | ||
maxFiltrationValue: Double, | ||
maxDimension: Int | ||
): Sequence<AbstractSimplex<VertexT>> { | ||
val edges: List<Pair<Double?, Pair<VertexT, VertexT>>> = | ||
weightedEdges(metricSpace, maxFiltrationValue).sortedBy { it.first }.toList() | ||
val lowerNeighbors = buildMap { | ||
edges.forEach { | ||
dvw -> | ||
( | ||
getOrPut( | ||
dvw.second.second, | ||
defaultValue = { -> hashSetOf<VertexT>() } | ||
) as MutableSet<VertexT> | ||
).add(dvw.second.first) | ||
} | ||
} | ||
|
||
val V: MutableSet<AbstractSimplex<VertexT>> = HashSet(metricSpace.size + edges.size) | ||
|
||
val tasks: ArrayDeque<Pair<AbstractSimplex<VertexT>, Set<VertexT>>> = ArrayDeque(edges.size) | ||
metricSpace.elements.forEach { vertex -> | ||
tasks.addFirst( | ||
Pair( | ||
abstractSimplexOf(vertex), | ||
lowerNeighbors.getOrElse(vertex, { -> emptySet() }) | ||
) | ||
) | ||
} | ||
|
||
while (tasks.size > 0) { | ||
val task: Pair<AbstractSimplex<VertexT>, Set<VertexT>> = tasks.removeFirst() | ||
val tau: AbstractSimplex<VertexT> = task.first | ||
val N: Set<VertexT> = task.second | ||
if (symmetryGroup.isRepresentative(tau)) V.add(tau) | ||
if (tau.size < maxDimension) { | ||
N.forEach { v: VertexT -> | ||
run { | ||
val sigma: AbstractSimplex<VertexT> = tau.plus(v) | ||
val M: Set<VertexT> = | ||
N.intersect(lowerNeighbors.get(v)?.asIterable() ?: emptySequence<VertexT>().asIterable()) | ||
tasks.addFirst(Pair(sigma, M)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
val filtered: Filtered<VertexT, Double> = FiniteMetricSpace.MaximumDistanceFiltrationValue(metricSpace) | ||
return ExpandSequence<VertexT> (V.sortedWith(VietorisRips.getComparator(filtered)), symmetryGroup) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/commonTest/kotlin/org/appliedtopology/tda4j/ProfilingSpec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package org.appliedtopology.tda4j | ||
|
||
import io.kotest.core.spec.style.FunSpec | ||
|
||
val dimension: Int = 4 | ||
val maxF = dimension.toDouble() | ||
val maxD = 1 shl dimension | ||
class ZomorodianProfilingSpec : FunSpec({ | ||
val hc4 = HyperCube(dimension) | ||
val hcs4 = HyperCubeSymmetry(dimension) | ||
|
||
test("Time VietorisRips construction") { | ||
val vr = VietorisRips<Int>(hc4, maxF, maxD, ZomorodianIncremental<Int>()) | ||
val sstream = vr.simplices | ||
var size = 0 | ||
sstream.forEach { size += 1 } | ||
println(size) | ||
} | ||
}) | ||
|
||
class SymmetricZomorodianProfilingSpec : FunSpec({ | ||
val hc4 = HyperCube(dimension) | ||
val hcs4 = HyperCubeSymmetry(dimension) | ||
|
||
test("Time SymmetricVietorisRips construction") { | ||
val vr = VietorisRips<Int>(hc4, maxF, maxD, SymmetricZomorodianIncremental<Int>(hcs4)) | ||
val sstream = vr.simplices | ||
var size = 0 | ||
sstream.forEach { size += 1 } | ||
println(size) | ||
} | ||
}) |
Oops, something went wrong.