Skip to content

Commit 063d187

Browse files
committed
add TODOs and convenience methods
1 parent 4fdda34 commit 063d187

File tree

2 files changed

+34
-18
lines changed

2 files changed

+34
-18
lines changed

src/main/kotlin/edu/mcgill/kaliningraph/Graph.kt

+21-17
Original file line numberDiff line numberDiff line change
@@ -48,29 +48,33 @@ constructor(override val vertices: Set<V> = setOf())
4848
val edges: Set<E> by lazy { edgMap.values.flatten().toSet() }
4949

5050
// Degree matrix
51-
val D: SpsMat by lazy { vwise { v, _ -> v.neighbors.size.toDouble() } }
51+
val D: SpsMat by lazy { elwise(size) { i -> this[i].neighbors.size.toDouble() } }
5252

5353
// Adjacency matrix
5454
val A: SpsMat by lazy { vwise { v, n -> 1.0 } }
5555
val A_AUG: SpsMat by lazy { A + A.transpose() + I }
5656

57-
// Normalized adjacency
58-
val ANORM: SpsMat by lazy {
57+
// Symmetric normalized adjacency
58+
val ASYMNORM: SpsMat by lazy {
5959
vwise { v, n -> 1.0 / (sqrt(v.degree.toDouble()) * sqrt(n.degree.toDouble())) }
6060
}
6161

6262
// Laplacian matrix
6363
val L: SpsMat by lazy { D - A }
64-
val I: SpsMat by lazy { elwise(size) { v, n -> if(v == n) 1.0 else null } }
65-
val LSYMNORM: SpsMat by lazy { I - ANORM }
64+
val I: SpsMat by lazy { elwise(size) { i -> 1.0 } }
65+
// Symmetric normalized Laplacian
66+
val LSYMNORM: SpsMat by lazy { I - ASYMNORM }
67+
68+
val ENCODED: SpsMat by lazy { vertices.map { it.encode() }.toTypedArray().toEJMLSparse() }
69+
70+
// TODO: Implement APSP distance matrix using algebraic Floyd-Warshall
71+
// https://doi.org/10.1137/1.9780898719918.ch5
6672

6773
inline fun vwise(crossinline lf: Graph<G, E, V>.(V, V) -> Double?): SpsMat =
6874
elwise(size) { i, j ->
6975
(this[i] to this[j]).let { (v, n) -> if (n in v.neighbors) lf(v, n) else null }
7076
}
7177

72-
fun encode(): SpsMat = vertices.map { it.encode() }.toTypedArray().toEJMLSparse()
73-
7478
val degMap: Map<V, Int> by lazy { vertices.map { it to it.neighbors.size }.toMap() }
7579
operator fun SpsMat.get(n0: V, n1: V) = this[index[n0]!!, index[n1]!!]
7680
operator fun SpsMat.set(n0: V, n1: V, value: Double) {
@@ -88,6 +92,7 @@ constructor(override val vertices: Set<V> = setOf())
8892

8993
operator fun minus(graph: G): G = new(vertices - graph.vertices)
9094

95+
// TODO: Reimplement using matrix transpose
9196
fun reversed(): G = new(
9297
vertices.map { it to setOf<E>() }.toMap() +
9398
vertices.flatMap { src ->
@@ -96,14 +101,13 @@ constructor(override val vertices: Set<V> = setOf())
96101
)
97102

98103
val histogram: Map<V, Int> by lazy { aggregateBy { it.size } }
99-
val labelFunc: (V) -> Int = { v: V -> histogram[v]!! }
100104

101105
/* (𝟙 + A)ⁿ[a, b] counts the number of walks between vertices a, b of length n
102106
* Let i be the smallest natural number such that (𝟙 + A)ⁱ has no zeros.
103107
* Fact: i is the length of the longest shortest path in G.
104108
*
105109
* TODO: implement O(M(n)log(n)) version based on Booth & Lipton (1981)
106-
* https://link.springer.com/content/pdf/10.1007/BF00264532.pdf#page=5
110+
* https://doi.org/10.1007/BF00264532
107111
*/
108112

109113
tailrec fun diameter(d: Int = 1, walks: SpsMat = A_AUG): Int =
@@ -134,16 +138,16 @@ constructor(override val vertices: Set<V> = setOf())
134138
// Message passing rounds
135139
t: Int = diameter() * 10,
136140
// Matrix of node representations ℝ^{|V|xd}
137-
H: SpsMat = encode(),
138-
// (Trainable) weight matrix
141+
H: SpsMat = ENCODED,
142+
// (Trainable) weight matrix ℝ^{dxd}
139143
W: SpsMat = randomMatrix(H.numCols),
140-
// Bias term
144+
// Bias term ℝ^{dxd}
141145
b: SpsMat = randomMatrix(size, H.numCols),
142-
// Nonlinearity
146+
// Nonlinearity ℝ^{*} -> ℝ^{*}
143147
σ: (SpsMat) -> SpsMat = ACT_TANH,
144-
// Layer normalization
148+
// Layer normalization ℝ^{*} -> ℝ^{*}
145149
z: (SpsMat) -> SpsMat = NORM_AVG,
146-
// Message
150+
// Message ℝ^{*} -> ℝ^{*}
147151
m: Graph<G, E, V>.(SpsMat) -> SpsMat = { σ(z(A * it * W + it * W + b)) }
148152
): SpsMat = if(t == 0) H else gnn(t = t - 1, H = m(H), W = W, b = b)
149153

@@ -180,7 +184,7 @@ constructor(override val vertices: Set<V> = setOf())
180184
// https://web.engr.oregonstate.edu/~erwig/papers/InductiveGraphs_JFP01.pdf#page=6
181185
override fun toString() =
182186
"(" + vertices.joinToString(", ", "{", "}") + ", " +
183-
edgList.map { (v, e) -> "${v.id}${e.target.id}" }.joinToString(", ", "{", "}") + ")"
187+
edgList.joinToString(", ", "{", "}") { (v, e) -> "${v.id}${e.target.id}" } + ")"
184188

185189
open fun render(): MutableGraph = graph(directed = true, strict = true) {
186190
val color = if (DARKMODE) WHITE else BLACK
@@ -220,7 +224,7 @@ constructor(val id: String) : IVertex<G, E, V> {
220224
override val graph: G by lazy { Graph(neighbors(-1)) }
221225
abstract val edgeMap: (V) -> Collection<E> // Allows self-loops by passing this
222226
override val outgoing by lazy { edgeMap(this as V).toSet() }
223-
override val incoming by lazy { graph.reversed().edgMap.toMap()[this]!! }
227+
override val incoming by lazy { graph.reversed().edgMap[this] ?: emptySet() }
224228
open val neighbors by lazy { outgoing.map { it.target }.toSet() }
225229
open val degree by lazy { neighbors.size }
226230

src/main/kotlin/edu/mcgill/kaliningraph/Utils.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ fun Graph<*, *, *>.toGraphViz(layout: Engine = DOT, format: Format = SVG) =
5353

5454
fun Graph<*, *, *>.html() = toGraphViz().toString()
5555
fun Graph<*, *, *>.show() = toGraphViz().toFile(File.createTempFile("temp", ".svg")).show()
56+
fun SpsMat.show() = matToImg().let { data ->
57+
File.createTempFile("temp", ".html").apply { writeText("<html><body><img src=\"$data\"/></body></html>") }
58+
}.show()
59+
5660
val browserCmd = System.getProperty("os.name").toLowerCase().let { os ->
5761
when {
5862
"win" in os -> "rundll32 url.dll,FileProtocolHandler"
@@ -131,5 +135,13 @@ fun SpsMat.meanNorm(copy: Boolean = false) =
131135
inline fun elwise(rows: Int, cols: Int = rows, nonZeroes: Int = rows,
132136
crossinline lf: (Int, Int) -> Double?) =
133137
SpsMat(rows, cols, nonZeroes).also { sprsMat ->
134-
for (v in 0 until rows) for (n in 0 until cols) lf(v, n)?.let { sprsMat[v, n] = it }
138+
for (v in 0 until rows) for (n in 0 until cols)
139+
lf(v, n)?.let { if (it != 0.0) sprsMat[v, n] = it }
140+
}
141+
142+
inline fun elwise(size: Int, nonZeroes: Int = size,
143+
crossinline lf: (Int) -> Double?) =
144+
SpsMat(size, size, nonZeroes).also { sprsMat ->
145+
for (v in 0 until size)
146+
lf(v)?.let { if (it != 0.0) sprsMat[v, v] = it }
135147
}

0 commit comments

Comments
 (0)