Skip to content

Commit

Permalink
Swapped out to making Simplex a type alias for SortedSet with top…
Browse files Browse the repository at this point in the history
…ological methods added through self-type style type class implementations.
  • Loading branch information
Mikael Vejdemo-Johansson committed Oct 23, 2024
1 parent 2991013 commit d7dc236
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 159 deletions.
4 changes: 2 additions & 2 deletions src/main/scala/org/appliedtopology/tda4j/AlphaShapes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AlphaShapes(val points: Array[Array[Double]])
!points.exists(metricSpace.pointSqDistance(mb.center(), _) < mb.squaredRadius())

def isDelaunaySimplex(spx: Simplex[Int]): Boolean =
isDelaunay(spx.vertices.toArray.map(points(_)))
isDelaunay(spx.toArray.map(points(_)))

override def iterateDimension: PartialFunction[Int, Iterator[Simplex[Int]]] = {
case d if d == cacheDimension => simplexCache.iterator
Expand All @@ -52,7 +52,7 @@ class AlphaShapes(val points: Array[Array[Double]])
val newSimplexCache = for
spx <- simplexCache
i <- metricSpace.elements.takeWhile(_ < spx.vertices.min)
coface = spx.union(Simplex(i))
i <- metricSpace.elements.takeWhile(_ < spx.min)
if isDelaunaySimplex(coface)
yield coface
simplexCache = newSimplexCache.sortBy(filtrationValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ object FiniteMetricSpace {
val metricSpace: FiniteMetricSpace[VertexT]
) extends PartialFunction[Simplex[VertexT], Double] {
def isDefinedAt(spx: Simplex[VertexT]): Boolean =
spx.vertices.forall(v => metricSpace.contains(v))
spx.forall(v => metricSpace.contains(v))

def apply(spx: Simplex[VertexT]): Double =
if (spx.dim <= 0)
0.0
else
spx.vertices
.flatMap(v => spx.vertices.filter(_ > v).map(w => metricSpace.distance(v, w)))
spx
.flatMap(v => spx.filter(_ > v).map(w => metricSpace.distance(v, w)))
.max
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/main/scala/org/appliedtopology/tda4j/Homology.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import org.appliedtopology.tda4j.barcode.PersistenceBar
import collection.{immutable, mutable}
import scala.annotation.tailrec

//class ReducedSimplicialHomologyContext[VertexT: Ordering, CoefficientT: Fractional, FiltrationT: Ordering]()
import math.Fractional.Implicits.infixFractionalOps
import math.Ordering.Implicits.sortedSetOrdering

//class ReducedSimplicialHomologyContext[VertexT: Ordering, CoefficientT: Field, FiltrationT: Ordering]()
// extends CellularHomologyContext[Simplex[VertexT], CoefficientT, FiltrationT]() {}

class SimplicialHomologyContext[VertexT: Ordering, CoefficientT: Field, FiltrationT: Ordering]()
Expand Down Expand Up @@ -164,11 +167,11 @@ class SimplicialHomologyByDimensionContext[VertexT: Ordering, CoefficientT: Fiel
// secondly, we can read off homology completely from a minimal spanning tree
val kruskal = new Kruskal[Simplex[VertexT]](
cycles.keys.toSeq,
{ (x: Simplex[VertexT], y: Simplex[VertexT]) => stream.filtrationValue(Simplex.from(x.vertices ++ y.vertices)) }
{ (x: Simplex[VertexT], y: Simplex[VertexT]) => stream.filtrationValue(x ++ y) }
)(using stream.filtrationOrdering)

kruskal.mstIterator.foreach { (src, tgt) =>
val edge: Simplex[VertexT] = Simplex.from(src.vertices ++ tgt.vertices)
val edge: Simplex[VertexT] = src ++ tgt

// the edge src -- tgt will connect src to tgt thus removing one of the cycles
val dEdge = edge.boundary
Expand All @@ -181,7 +184,7 @@ class SimplicialHomologyByDimensionContext[VertexT: Ordering, CoefficientT: Fiel
}

kruskal.cyclesIterator.foreach { (src, tgt) =>
val edge: Simplex[VertexT] = Simplex.from(src.vertices ++ tgt.vertices)
val edge: Simplex[VertexT] = src ++ tgt

// the edge src -- tgt will connect src to tgt thus closing a loop
val dEdge = edge.boundary
Expand Down
38 changes: 19 additions & 19 deletions src/main/scala/org/appliedtopology/tda4j/RipserStream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ class SimplexIndexing(val vertexCount: Int) {
}

@tailrec
final def apply(n: Int, d: Int, upperAccum: Set[Int] = Set()): Simplex[Int] = {
if (d < 0) return Simplex.from(upperAccum)
if (n <= 0) return Simplex.from(upperAccum ++ (0 until d).toSet)
if (d == 0) return Simplex.from(upperAccum + n)
final def apply(n: Int, d: Int, upperAccum: Simplex[Int] = ()): Simplex[Int] = {
if (d < 0) return upperAccum
if (n <= 0) return upperAccum ++ (0 until d).toSet
if (d == 0) return upperAccum + n
val searchResult: SearchResult = binomialTable(d).search(n)
val id: Int = searchResult match {
case Found(foundIndex) => foundIndex
Expand All @@ -41,9 +41,9 @@ class SimplexIndexing(val vertexCount: Int) {
}

def cofacetIterator(simplex: Simplex[Int]): Iterator[Int] =
cofacetIterator(apply(simplex), simplex.vertices.size, true)
cofacetIterator(apply(simplex), simplex.size, true)
def topCofacetIterator(simplex: Simplex[Int]): Iterator[Int] =
cofacetIterator(apply(simplex), simplex.vertices.size, false)
cofacetIterator(apply(simplex), simplex.size, false)
def cofacetIterator(
index: Int,
size: Int,
Expand All @@ -61,7 +61,7 @@ class SimplexIndexing(val vertexCount: Int) {
) { (s, iB, iA, k, j) =>
if (j < 0) {
None // end iteration when we're done
} else if (s.vertices.contains(j)) {
} else if (s.contains(j)) {
if (!allCofacets)
None
else
Expand All @@ -79,7 +79,7 @@ class SimplexIndexing(val vertexCount: Int) {
.map((os: Option[Int]) => os.get)

def facetIterator(index: Int, size: Int): Iterator[Int] =
Iterator.unfold((apply(index, size).vertices.toSeq.sorted, index, 0, size - 1)) {
Iterator.unfold((apply(index, size).toSeq.sorted, index, 0, size - 1)) {
(s: Seq[Int], iB: Int, iA: Int, k: Int) =>
if (k < 0) None
else {
Expand All @@ -91,8 +91,8 @@ class SimplexIndexing(val vertexCount: Int) {
}

def apply(simplex: Simplex[Int]): Int =
simplex.vertices.toSeq.sorted.reverse.zipWithIndex.map { (v, i) =>
binomial(v, simplex.vertices.size - i)
simplex.toSeq.sorted.reverse.zipWithIndex.map { (v, i) =>
binomial(v, simplex.size - i)
}.sum
}

Expand All @@ -116,7 +116,7 @@ class RipserStreamSparse(
val doubleSimplexPairOrdering: Ordering[(Double, Simplex[Int])] = {
(x: (Double, Simplex[Int]), y: (Double, Simplex[Int])) =>
Ordering.Double.TotalOrdering.compare(x._1, y._1) match {
case 0 => simplexOrdering[Int].compare(x._2, y._2)
case 0 => summon[Ordering[Simplex[Int]]].compare(x._2, y._2)
case c: Int => c
}
}
Expand Down Expand Up @@ -183,11 +183,11 @@ class RipserStreamSparse(
fV <- simplexCache.map(_._1).iterator
previousSimplex <- simplexCache.filter(_._1 < fV).iterator.map(_._2)
nextVertex <- metricSpace.elements
.filter(!previousSimplex.vertices.contains(_))
.filter(nV => previousSimplex.vertices.map(oV => metricSpace.distance(oV, nV)).max <= fV)
simplex: Simplex[Int] = Simplex.from(previousSimplex.vertices + nextVertex)
if zeroApparentCofacet(si(simplex), simplex.vertices.size).isEmpty
if zeroApparentFacet(si(simplex), simplex.vertices.size).isEmpty
.filter(!previousSimplex.contains(_))
.filter(nV => previousSimplex.map(oV => metricSpace.distance(oV, nV)).max <= fV)
simplex: Simplex[Int] = previousSimplex + nextVertex
if zeroApparentCofacet(si(simplex), simplex.size).isEmpty
if zeroApparentFacet(si(simplex), simplex.size).isEmpty
// also check if this is cleared?
yield simplex
}.iterator
Expand Down Expand Up @@ -296,11 +296,11 @@ class RipserStreamOf[VertexT: Ordering](
RipserStream(intMetricSpace, maxFiltrationValue, maxDimension)

override def iterator: Iterator[Simplex[VertexT]] =
rs.iterator.map(s => Simplex.from(s.vertices.map(v => vertices(v))))
rs.iterator.map(s => s.map(v => vertices(v)))

override def filtrationValue: PartialFunction[Simplex[VertexT], Double] = { spx =>
val indices: SortedSet[Int] = spx.vertices.map(vertices.indexOf)
rs.filtrationValue(Simplex.from(indices))
val indices: SortedSet[Int] = spx.map(vertices.indexOf)
rs.filtrationValue(indices)
}
}

Expand Down
103 changes: 33 additions & 70 deletions src/main/scala/org/appliedtopology/tda4j/Simplex.scala
Original file line number Diff line number Diff line change
@@ -1,81 +1,44 @@
package org.appliedtopology.tda4j

import scala.collection.{SortedIterableFactory, SortedSetFactoryDefaults, StrictOptimizedSortedSetOps, mutable}
import scala.collection.immutable.{Set, SortedMap, SortedSet, SortedSetOps, TreeSet}
import scala.math.Ordering.IntOrdering
import scala.math.Ordering.Double.IeeeOrdering
import scala.collection.mutable
import scala.collection.immutable.SortedSet
import math.Ordering.Implicits.sortedSetOrdering

/** Class representing an abstract simplex. Abstract simplices are given by sets (of totally ordered vertices)
* and inherit from `Cell` so that the class has a `boundary` and a `dim` method.
/** Simplices really are just sets, outright. We provide an implementation of the [OrderedCell] typeclass
* for simplicial complex structures, to enable their use.
*
* You should never have reason to use the constructor directly (...and if you do, you should make sure to give the
* internal `SortedSet` yourself) - instead use the factory method in the companion object. In code this means that
* instead of `new Simplex[Self](a,b,c)` you would write `Simplex[Self](a,b,c)`.
*
* @param vertices
* Vertices of the simplex
* @param ordering
* Ordering of the vertex type
* @tparam VertexT
* Vertex type
*/
case class Simplex[VertexT : Ordering] private[tda4j] (vertices : SortedSet[VertexT]) {
override def toString(): String =
vertices.mkString(s"∆(", ",", ")")

def union(other: Simplex[VertexT]) =
new Simplex(vertices.union(other.vertices))

def incl(x : VertexT) =
new Simplex(vertices + x)

def contains(x : VertexT) = vertices.contains(x)
}

def simplexOrdering[VertexT : Ordering as vtxOrdering]: Ordering[Simplex[VertexT]] =
Ordering.by{(spx: Simplex[VertexT]) => spx.vertices}(sortedSetOrdering[SortedSet, VertexT](vtxOrdering))
type Simplex[VertexT] = SortedSet[VertexT]

given [VertexT : Ordering] => Ordering[Simplex[VertexT]] = simplexOrdering
extension [VertexT : Ordering](spx : Simplex[VertexT])
def show : String = spx.mkString(s"∆(", ",", ")")

// Ordering.by{(spx: Simplex[VertexT]) => spx.vertices}(sortedSetOrdering[SortedSet, VertexT](vtxOrdering))


/** Simplex companion object with factory methods
*/
object Simplex {
def apply[VertexT: Ordering](vertices: VertexT*) =
new Simplex[VertexT](SortedSet.from(vertices))

def empty[VertexT: Ordering]: Simplex[VertexT] =
new Simplex[VertexT](SortedSet.empty)

def from[VertexT: Ordering](source: IterableOnce[VertexT]): Simplex[VertexT] =
new Simplex(SortedSet.from(source.iterator))

def simplexIsOrderedCell[VertexT](using vtxOrd : Ordering[VertexT])(using ord : Ordering[Simplex[VertexT]]) : Simplex[VertexT] is OrderedCell =
new (Simplex[VertexT] is OrderedCell):
override lazy val ordering = ord
extension (t: Simplex[VertexT]) {
def boundary[CoefficientT](using fr: (CoefficientT is Field)): Chain[Simplex[VertexT], CoefficientT] =
if (t.dim <= 0) Chain()(using ord)
else Chain.from(
t.vertices
.to(Seq)
.zipWithIndex
.map((vtx, i) => Simplex.from(t.vertices.toSeq.patch(i, Seq.empty, 1)))
.zip(Iterator.unfold(fr.one)(s => Some((s, fr.negate(s)))))
)(using ord)
def dim: Int = t.vertices.size - 1
}
def compare(x: Simplex[VertexT], y: Simplex[VertexT]): Int = ord.compare(x, y)

given [VertexT : Ordering as vtxOrdering] => (Simplex[VertexT] is OrderedCell) =
simplexIsOrderedCell(using vtxOrdering)(using simplexOrdering[VertexT](using vtxOrdering))
}
object Simplex:
def from[VertexT : Ordering, T <: Seq[VertexT]](vertices : T) : Simplex[VertexT] = SortedSet.from(vertices)
def apply[VertexT : Ordering](vertices : VertexT*) : Simplex[VertexT] = SortedSet.from(vertices)

/** Convenience method for defining simplices
*
* The character ∆ is typed as Alt+J on Mac GB layout, and has unicode code 0x0394.
*/
def [T: Ordering](ts: T*): Simplex[T] = Simplex.from(ts)
*
* The character ∆ is typed as Alt+J on Mac GB layout, and has unicode code 0x0394.
*/
def [VertexT : Ordering](vertices : VertexT*) : Simplex[VertexT] = SortedSet.from(vertices)

def simplexOrdering[VertexT](using vtxOrd : Ordering[VertexT]) : Ordering[Simplex[VertexT]] = sortedSetOrdering(vtxOrd)
def SortedSet_is_OrderedCell[VertexT](using vtxOrd : Ordering[VertexT])(setOrdering : Ordering[SortedSet[VertexT]] = simplexOrdering(using vtxOrd)): (SortedSet[VertexT] is OrderedCell) =
new(SortedSet[VertexT] is OrderedCell) {
override lazy val ordering = setOrdering
extension (spx: SortedSet[VertexT]) {
override def dim = spx.size - 1
override def boundary[CoefficientT: Field as fr] =
if (spx.dim <= 0) Chain()
else Chain.from(
spx.to(Seq)
.zipWithIndex
.map((vtx, i) => spx -- spx.slice(i, i + 1))
.zip(Iterator.unfold(fr.one)(s => Some((s, fr.negate(s)))))
)
}
}
given default_SortedSet_is_OrderedCell[VertexT : Ordering] : (SortedSet[VertexT] is OrderedCell) =
SortedSet_is_OrderedCell[VertexT]()
14 changes: 7 additions & 7 deletions src/main/scala/org/appliedtopology/tda4j/SimplexStream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class ExplicitStreamBuilder[VertexT: Ordering, FiltrationT](using
given filtrationOrdering: Ordering[(FiltrationT, Simplex[VertexT])] =
Ordering
.by[(FiltrationT, Simplex[VertexT]), FiltrationT]((f: FiltrationT, s: Simplex[VertexT]) => f)(ordering)
.orElseBy((f: FiltrationT, s: Simplex[VertexT]) => s)(simplexOrdering[VertexT])
.orElseBy((f: FiltrationT, s: Simplex[VertexT]) => s)(simplexOrdering)
simplices.sortInPlace

new ExplicitStream(filtrationValues.toMap, simplices.map((_, s) => s).toSeq)(using filterable)
Expand All @@ -169,21 +169,21 @@ class FilteredSimplexOrdering[VertexT, FiltrationT](
case (x, y) if filtration.filtrationValue.isDefinedAt(x) && filtration.filtrationValue.isDefinedAt(y) =>
filtrationOrdering.compare(filtration.filtrationValue(x), filtration.filtrationValue(y)) match {
case 0 =>
if (Ordering.Int.compare(x.vertices.size, y.vertices.size) == 0)
if (Ordering.Int.compare(x.size, y.size) == 0)
Ordering.Implicits
.sortedSetOrdering[SortedSet, VertexT](vertexOrdering)
.compare(x.vertices, y.vertices)
.compare(x, y)
else
Ordering.Int.compare(x.vertices.size, y.vertices.size)
Ordering.Int.compare(x.size, y.size)
case cmp if cmp != 0 => cmp
}
case (x, y) => // at least one does not have a filtration value defined; just go by dimension and lexicographic
if (Ordering.Int.compare(x.vertices.size, y.vertices.size) == 0)
if (Ordering.Int.compare(x.size, y.size) == 0)
Ordering.Implicits
.sortedSetOrdering[SortedSet, VertexT](vertexOrdering)
.compare(x.vertices, y.vertices)
.compare(x, y)
else
Ordering.Int.compare(x.vertices.size, y.vertices.size)
Ordering.Int.compare(x.size, y.size)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/org/appliedtopology/tda4j/SimplicialSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ class Singular[VertexT: Ordering] private[tda4j](val allSimplices: Seq[Simplex[V
generators.map { spx =>
spx ->
(0 to spx.dim).map { i =>
SimplicialWrapper(Simplex(spx.wrapped.vertices.drop(i)))
SimplicialWrapper(spx.wrapped.drop(i))
}.toList
}
)
Expand All @@ -385,7 +385,7 @@ object Singular {
* @return
*/
def from[VertexT: Ordering](underlying: Seq[Simplex[VertexT]]): Singular[VertexT] =
fromAll(underlying.toSet.flatMap(spx => spx.vertices.subsets.filter(_.nonEmpty).map(Simplex.from)).toSeq)
fromAll(underlying.toSet.flatMap(spx => spx.subsets.filter(_.nonEmpty)).toSeq)

/**
* Create a [[Singular]] simplicial set from the simplices in `allSimplices`. This method will trust the
Expand Down
14 changes: 7 additions & 7 deletions src/main/scala/org/appliedtopology/tda4j/SymmetryGroup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ trait SymmetryGroup[KeyT, VertexT: Ordering]() {
def orbitSeq(
simplex: Simplex[VertexT]
): Set[Simplex[VertexT]] =
keys.map(k => Simplex.from(simplex.vertices.map(apply(k)))).toSet
keys.map(k => simplex.map(apply(k))).toSet

def orbitPar(
simplex: Simplex[VertexT]
): Set[Simplex[VertexT]] = {
val futures: Iterable[Future[Simplex[VertexT]]] =
for (k <- keys) yield Future {
Simplex.from(simplex.vertices.map(apply(k)))
simplex.map(apply(k))
}

val allfutures = Future.sequence(futures)
Expand All @@ -82,7 +82,7 @@ trait SymmetryGroup[KeyT, VertexT: Ordering]() {
def representative(
simplex: Simplex[VertexT]
): Simplex[VertexT] =
keys.map(k => Simplex.from(simplex.vertices.map(apply(k)))).min
keys.map(k => simplex.map(apply(k))).min

/** Check if `simplex` is the canonical representative of its own orbit.
*
Expand Down Expand Up @@ -291,9 +291,9 @@ class SymmetricZomorodianIncremental[VertexT: Ordering, KeyT](
val task = tasks.pop()
val tau = task._1
val N = task._2
val simplex = Simplex.from(tau)
val simplex : Simplex[VertexT] = tau
if (symmetry.isRepresentative(simplex)) {
representatives += Simplex.from(tau)
representatives += tau
}
if (tau.size <= maxDimension) {
N.foreach { v =>
Expand Down Expand Up @@ -490,7 +490,7 @@ class HyperCubeSymmetryGeneratorsBitSet(val bitlength: Int) extends HyperCubeSym
if (representatives.contains(simplex)) {
simplex == representatives(simplex)
} else {
if (generators.forall(g => simplex <= Simplex.from(simplex.vertices.map(s => g(s))))) {
if (generators.forall(g => simplex <= simplex.map(s => g(s)))) {
// simplex is a pseudo-minimum
// time to check the entire orbit
representatives(simplex) = super.representative(simplex)
Expand Down Expand Up @@ -524,7 +524,7 @@ class HyperCubeSymmetryGenerators(val bitlength: Int) extends HyperCubeSymmetry(
if (representatives.contains(simplex)) {
simplex == representatives(simplex)
} else {
if (generators.par.forall(g => simplex <= Simplex.from(simplex.vertices.map(s => g(s))))) {
if (generators.par.forall(g => simplex <= simplex.map(s => g(s)))) {
// simplex is a pseudo-minimum
// time to check the entire orbit
representatives(simplex) = super.representative(simplex)
Expand Down
Loading

0 comments on commit d7dc236

Please sign in to comment.