Skip to content

Commit

Permalink
Performance chapter 1: Line decomposition
Browse files Browse the repository at this point in the history
  • Loading branch information
empireu committed Dec 14, 2023
1 parent db16978 commit 759d7ca
Show file tree
Hide file tree
Showing 11 changed files with 1,291 additions and 53 deletions.
92 changes: 59 additions & 33 deletions src/main/kotlin/org/ageseries/libage/data/DisjointSet.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package org.ageseries.libage.data

/**
* A class for using the Disjoint Sets "Union-Find" algorithm.
* Implementation of the Disjoint-Set data structure designed for use in conjunction with the Union-Find algorithm.
* It is intended to be extended by inheritors, where the term "Super" (akin to a **superclass**) clarifies its nature.
* To enhance usability, a *self-type parameter* ([Self]) has been integrated.
*
* There are two ways to use this:
* The [representative] can be retrieved as [Self], and the [union] operation readily accepts [Self].
* Essentially, inheritors can work with instances of their class without necessitating casting (unlike the old implementation).
*
* - Inherit from it, using the methods on the object itself;
* - compose it into your class; or
* - do both.
*
* Typical use case is as follows:
*
* 1. Choose your method above; for the former, the DisjointSet object is `this`; for the latter, substitute your field.
* 2. Unify DisjointSets by calling [unite].
* 3. Determine if two DisjointSets are in the same component by testing their [representative] for equality.
* 4. Optionally, mutate the data on a DisjointSet subclass' representative, knowing that the mutations are visible from all DisjointSets with the same representative.
*
* Note that it is difficult to control which instance will ultimately *be* the representative; in the cases where it can't be avoided, [priority] can be used, but this is advisable only as a last resort (since it reduces the optimality of this algorithm).
* It's important to note that controlling which instance ultimately becomes the representative can be challenging.
* In cases where this is unavoidable, the [priority] parameter can be employed.
* However, it's advisable to use this option sparingly, as it compromises the optimality of the algorithm and should only be considered as a last resort.
*/
open class DisjointSet {

@Suppress("UNCHECKED_CAST") // Side effect of self parameter. Fine in our case.
abstract class SuperDisjointSet<Self : SuperDisjointSet<Self>> {
/**
* The size of this tree; loosely, how many Sets have been merged, including transitively, with this one.
*
Expand All @@ -32,7 +26,8 @@ open class DisjointSet {
*
* Following this recursively will lead to the [representative]. All representatives refer to themselves as their parent.
*/
var parent: DisjointSet = this
@Suppress("LeakingThis") // Fine in this case.
var parent: Self = this as Self

/**
* The priority of the merge. If set, this overrides the "merge by size" algorithm in [unite].
Expand All @@ -48,34 +43,65 @@ open class DisjointSet {
*
* This is implemented using the "Path splitting" algorithm.
*/
val representative: DisjointSet
val representative: Self
get() {
var cur = this
while (cur.parent != cur) {
val next = cur.parent
cur.parent = next.parent
cur = next
var current = this

while (current.parent != current) {
val next = current.parent
current.parent = next.parent
current = next
}
return cur

return current as Self
}

/**
* Unite this instance with another instance of DisjointSet.
* *Union operation*.
*
* After this is done, both this and [other] will have the same [representative] as each other (and as all other Sets with which they were previously united).
*
* This is implemented using the "by size" merge algorithm, adjusted for [priority].
*/
open fun unite(other: DisjointSet) {
val trep = representative
val orep = other.representative
if (trep == orep) return
val (bigger, smaller) = when {
trep.priority > orep.priority -> Pair(trep, orep)
orep.priority > trep.priority -> Pair(orep, trep)
else -> if (trep.size < orep.size) Pair(orep, trep) else Pair(trep, orep)
open fun unite(other: Self) {
val thisRep = representative
val otherRep = other.representative

if (thisRep == otherRep){
return
}

val bigger: Self
val smaller: Self

// Override with priority:
if(thisRep.priority > otherRep.priority) {
bigger = thisRep
smaller = otherRep
}
else if(otherRep.priority > thisRep.priority) {
bigger = otherRep
smaller = thisRep
}
else {
// By size:
if (thisRep.size < otherRep.size) {
bigger = otherRep
smaller = thisRep
} else {
bigger = thisRep
smaller = otherRep
}
}

smaller.parent = bigger.parent
bigger.size += smaller.size
}
}

/**
* Composable disjoint set, implemented as a [SuperDisjointSet].
* This implementation proves beneficial in specific algorithms where certain elements are partitioned using union-find.
* These elements might represent pure data or simply may not want or need to implement [SuperDisjointSet].
* */
class DisjointSet(override var priority: Int = 0) : SuperDisjointSet<DisjointSet>() // *It also proves useful in unit tests.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import org.ageseries.libage.sim.electrical.mna.Node
import org.ageseries.libage.sim.electrical.mna.component.Component
import org.ageseries.libage.sim.electrical.mna.component.Resistor
import org.ageseries.libage.sim.electrical.mna.component.VoltageSource
import org.ageseries.libage.data.DisjointSet
import org.ageseries.libage.data.SuperDisjointSet
import org.ageseries.libage.space.Vec2i

/**
Expand Down Expand Up @@ -161,11 +161,11 @@ data class CCData(val falstad: Falstad, val line: FalstadLine) {
}

/**
* A [DisjointSet] subclass which holds a [PinPos].
* A [SuperDisjointSet] subclass which holds a [PinPos].
*
* The Set class is used to implement the Disjoint Sets algorithm; the representative of the merged sets is used to determine which PinPos' are connected by wires, and thus merged into [Node]s as present in the [Circuit].
*/
data class PosSet(val pos: PinPos) : DisjointSet()
data class PosSet(val pos: PinPos) : SuperDisjointSet<PosSet>()

/**
* An interface for constructing [Component]s (usually)
Expand Down Expand Up @@ -255,7 +255,7 @@ abstract class PoleConstructor : IComponentConstructor {
*/
class Falstad(val source: String) {
/**
* "List of roots" (of the [Disjoint Set][DisjointSet] forest); a mapping from [PinPos] to [PosSet].
* "List of roots" (of the [Disjoint Set][SuperDisjointSet] forest); a mapping from [PinPos] to [PosSet].
*
* The values of this map are members of the forest (not necessarily "roots", but the root can easily be found as the representative).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,8 @@ class Circuit {
val voltageSourceSet: MutableSet<VSource> = mutableSetOf()

components.forEach {component ->
dprintln("component $component pinreps ${component.pins.map { it.representative as Pin }}")
pinSet.addAll(component.pins.map { it.representative as Pin })
dprintln("component $component pinreps ${component.pins.map { it.representative }}")
pinSet.addAll(component.pins.map { it.representative })
voltageSourceSet.addAll(component.vsources)
}

Expand Down Expand Up @@ -671,3 +671,5 @@ class Circuit {
return sb.toString()
}
}

const val LARGE_RESISTANCE = 1e9
Loading

0 comments on commit 759d7ca

Please sign in to comment.