-
Notifications
You must be signed in to change notification settings - Fork 3
/
typed-edge-graph-class.lisp
390 lines (355 loc) · 16.1 KB
/
typed-edge-graph-class.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
(in-package #:graph-utils)
(declaim (optimize (speed 3) (space 2)))
(defclass typed-graph (directed-graph)
((matrix :accessor matrix :initarg :matrix
:initform (make-hash-table :test 'eql))
(edge-type-comparator :accessor edge-type-comparator
:initarg :edge-type-comparator :initform 'eql)
(indices :accessor indices :initarg :indices
:initform (make-hash-table :test 'equalp))))
(defgeneric typed-graph? (thing)
(:documentation "typed graph predicate")
(:method ((thing typed-graph)) t)
(:method (thing) nil))
(defmethod print-object ((graph typed-graph) stream)
"Print a typed-graph"
(print-unreadable-object (graph stream :type t)
(format stream "~A edge types (~A vertices & ~A edges)"
(hash-table-count (matrix graph))
(node-count graph)
(edges graph))))
(defmethod edge-types ((graph typed-graph))
(loop for et being the hash-keys in (matrix graph) collecting et))
(defmethod add-edge-type ((graph typed-graph) edge-type)
(or (gethash edge-type (matrix graph))
(setf (gethash edge-type (matrix graph))
(make-sparse-array (list (node-count graph) (node-count graph))
:adjustable t
:element-type 'number
:initial-element 0))))
(defmethod add-edge-index ((graph typed-graph) edge-type index-type unique? ordered? ordering-fn)
(add-edge-type graph edge-type)
(case index-type
(:sp
(cond ((and ordered? unique?)
(setf (gethash (cons edge-type :weight) (indices graph))
(make-index :type 'unique-ordered-index
:key-equality-fn (lambda (t1 t2)
(and (funcall (comparator graph)
(subject t1) (subject t2))
(eql (predicate t1) (predicate t2))))
:value-equality-fn (comparator graph)
:ordering-fn ordering-fn
:edge-type edge-type)))
((and (null ordered?) (null unique?))
(setf (gethash (cons edge-type :weight) (indices graph))
(make-index :type 'index
:key-equality-fn (comparator graph)
:value-equality-fn (comparator graph)
:edge-type edge-type)))))
(otherwise
(error "Only these index types are available: :sp :spw "))))
(defun make-typed-graph (&key (node-comparator 'equal) (saturation-point 0)
(edge-type-comparator 'eql) initial-edge-types)
"Create a new typed-graph object. You are responsible for making sure that
node-comparator is a valid hash table test."
(let ((g (make-instance 'typed-graph
:comparator node-comparator
:edge-type-comparator edge-type-comparator
:s-point saturation-point
:matrix (make-hash-table :test edge-type-comparator)
:nodes (make-hash-table :test node-comparator))))
(dolist (e initial-edge-types)
(add-edge-type g e))
g))
(defmethod add-node ((graph typed-graph) value &key capacity)
"Add a node to the graph."
(or (gethash value (nodes graph))
(let ((id (incf (last-id graph))))
(maphash (lambda (etype matrix)
(declare (ignore etype))
(incf-sarray-dimensions matrix))
(matrix graph))
(when capacity
(setf (gethash id (node-caps graph)) capacity))
(setf (gethash id (in-degree-table graph)) 0
(gethash id (out-degree-table graph)) 0
(gethash id (degree-table graph)) 0
(gethash value (nodes graph)) id
(gethash id (ids graph)) value)
id)))
(defmethod neighbors ((graph typed-graph) (node integer) &key edge-type
(return-ids? t))
"Return a list of ids for this node's neighbors. Returns inbound and
outbound neighbors for a directed graph."
(let ((neighbors nil))
(flet ((find-neighbors (matrix etype)
(map-sarray-col (lambda (row-id value)
(when (> value 0)
(push (cons etype row-id) neighbors)))
matrix node)
(map-sarray-row (lambda (col-id value)
(when (> value 0)
(push (cons etype col-id) neighbors)))
matrix node)))
(if edge-type
(find-neighbors (gethash edge-type (matrix graph)) edge-type)
(maphash (lambda (etype matrix)
(find-neighbors matrix etype))
(matrix graph)))
(if return-ids?
(nreverse neighbors)
(mapcar (lambda (pair)
(lookup-node graph (cdr pair)))
(nreverse neighbors))))))
(defmethod neighbors ((graph typed-graph) node &key edge-type (return-ids? t))
"Return a list of ids for this node's neighbors."
(let ((id (gethash node (nodes graph))))
(when id
(neighbors graph
id
:edge-type edge-type
:return-ids? return-ids?))))
(defmethod inbound-neighbors ((graph typed-graph) node &key edge-type
(return-ids? t))
(let ((id (gethash node (nodes graph))))
(when id
(inbound-neighbors graph
id
:edge-type edge-type
:return-ids? return-ids?))))
(defmethod inbound-neighbors ((graph typed-graph) (node integer) &key edge-type
(return-ids? t))
(let ((neighbors nil))
(flet ((find-neighbors (matrix etype)
(map-sarray-col (lambda (row-id value)
(when (> value 0)
(push (cons etype row-id) neighbors)))
matrix node)))
(if edge-type
(find-neighbors (gethash edge-type (matrix graph)) edge-type)
(maphash (lambda (etype matrix)
(find-neighbors matrix etype))
(matrix graph)))
(if return-ids?
(nreverse neighbors)
(mapcar (lambda (pair)
(lookup-node graph (cdr pair)))
(nreverse neighbors))))))
(defmethod outbound-neighbors ((graph typed-graph) node &key edge-type
(return-ids? t))
(let ((id (gethash node (nodes graph))))
(when id
(outbound-neighbors graph
id
:edge-type edge-type
:return-ids? return-ids?))))
(defmethod outbound-neighbors ((graph typed-graph) (node integer) &key
edge-type (return-ids? t))
(let ((neighbors nil))
(flet ((find-neighbors (matrix etype)
(when matrix
(map-sarray-row (lambda (col-id value)
(when (> value 0)
(push (cons etype col-id) neighbors)))
matrix node))))
(if edge-type
(find-neighbors (gethash edge-type (matrix graph)) edge-type)
(maphash (lambda (etype matrix)
(find-neighbors matrix etype))
(matrix graph)))
(if return-ids?
(nreverse neighbors)
(mapcar (lambda (pair)
(lookup-node graph (cdr pair)))
(nreverse neighbors))))))
(defmacro do-outbound-neighbors ((neighbor (graph node edge-type)) &body body)
(let ((n (gensym))
(g (gensym))
(e (gensym)))
`(let ((,n ,node)
(,g ,graph)
(,e ,edge-type))
(unless (integerp ,n)
(setq ,n (lookup-node ,g ,n)))
(flet ((map-neighbors (matrix)
(when matrix
(map-sarray-row (lambda (col-id value)
(when (> value 0)
(let ((,neighbor (lookup-node ,g col-id)))
,@body)))
matrix ,n))))
(if ,e
(map-neighbors (gethash ,e (matrix ,g)))
(maphash (lambda (etype matrix)
(declare (ignore etype))
(map-neighbors matrix))
(matrix ,g)))))))
(defmethod edge-exists? ((graph typed-graph) (n1 integer) (n2 integer)
&key edge-type)
"Is there an edge between n1 and n2 of type edge-type?"
(let ((matrix (gethash edge-type (matrix graph))))
(handler-case
(when (and (sparse-array? matrix)
(numberp (saref matrix n1 n2))
(> (saref matrix n1 n2) 0))
(saref matrix n1 n2))
(error (c)
(ignore-errors
(dbg "Problem with edge (~A,~A)->~A: ~A" n1 n2 (saref matrix n1 n2) c))
nil))))
(defmethod edge-exists? ((graph typed-graph) n1 n2 &key edge-type)
"Is there an edge between n1 and n2 of type edge-type?"
(let ((node1 (lookup-node graph n1))
(node2 (lookup-node graph n2)))
(when (and node1 node2)
(edge-exists? graph node1 node2 :edge-type edge-type))))
(defmethod add-edge ((graph typed-graph) (n1 integer) (n2 integer) &key
(weight 1) edge-type)
"Add an edge between n1 and n2 of type edge-type."
(unless (= n1 n2)
(let ((matrix (gethash edge-type (matrix graph))))
(unless (sparse-array? matrix)
(setq matrix (add-edge-type graph edge-type)))
(unless (> (saref matrix n1 n2) 0)
(incf (gethash n1 (out-degree-table graph)))
(incf (gethash n2 (in-degree-table graph)))
(incf (edges graph)))
(setf (saref matrix n1 n2) weight)
(list n1 n2 edge-type))))
(defmethod add-edge ((graph typed-graph) n1 n2 &key (weight 1) edge-type)
"Add an edge between n1 and n2 of type edge-type."
(let ((node1 (or (lookup-node graph n1) (add-node graph n1)))
(node2 (or (lookup-node graph n2) (add-node graph n2))))
(when (and node1 node2)
(add-edge graph
node1
node2
:edge-type edge-type
:weight weight))))
(defmethod delete-edge ((graph typed-graph) (n1 integer) (n2 integer)
&optional edge-type)
"Remove an edge from the graph."
(unless (= n1 n2)
(let ((matrix (gethash edge-type (matrix graph))))
(when (sparse-array? matrix)
(when (> (saref matrix n1 n2) 0)
(decf (gethash n1 (out-degree-table graph)))
(decf (gethash n2 (in-degree-table graph)))
(decf (edges graph))
(setf (saref matrix n1 n2) 0))))))
(defmethod delete-edge ((graph typed-graph) n1 n2 &optional edge-type)
(let ((node1 (or (lookup-node graph n1) (add-node graph n1)))
(node2 (or (lookup-node graph n2) (add-node graph n2))))
(when (and node1 node2)
(delete-edge graph node1 node2 edge-type))))
(defmethod map-edges ((fn function) (graph typed-graph) &key
edge-type collect? remove-nulls?)
"Apply a function to all edges (possibly only of a single type)."
(let ((r nil))
(flet ((map-it (matrix etype)
(when matrix
(fast-map-sarray #'(lambda (n1 n2 w)
(let ((v (funcall fn n1 n2 w etype)))
(when collect?
(push v r))))
;;(push (list n1 n2 v etype) r))))
matrix))))
(if edge-type
(map-it (gethash edge-type (matrix graph)) edge-type)
(maphash #'(lambda (etype matrix)
(map-it matrix etype))
(matrix graph)))
(nreverse (if remove-nulls?
(remove-if #'(lambda (triple)
(null (third triple)))
r)
r)))))
(defmethod list-edges ((graph typed-graph) &key nodes-as-ids edge-type)
"Return all edges as pairs of nodes."
(let ((r nil))
(flet ((map-it (matrix etype)
(when matrix
(fast-map-sarray #'(lambda (n1 n2 w)
(declare (ignore w))
(push (if nodes-as-ids
(list n1 n2 etype)
(list (gethash n1 (ids graph))
(gethash n2 (ids graph))
etype))
r))
matrix))))
(if edge-type
(map-it (gethash edge-type (matrix graph)) edge-type)
(maphash #'(lambda (etype matrix)
(map-it matrix etype))
(matrix graph)))
(nreverse r))))
(defmethod set-edge-weight ((graph typed-graph) (n1 integer) (n2 integer) weight
&key edge-type)
(let ((matrix (gethash edge-type (matrix graph))))
(setf (saref matrix n1 n2) weight)))
(defmethod edge-weight ((graph typed-graph) (n1 integer) (n2 integer)
&optional edge-type)
(let ((matrix (gethash edge-type (matrix graph))))
(saref matrix n1 n2)))
(defmethod edge-weight ((graph typed-graph) n1 n2 &optional edge-type)
(let ((id1 (lookup-node graph n1))
(id2 (lookup-node graph n2)))
(when (and id1 id2)
(edge-weight graph
id1
id2
edge-type))))
(defmethod incf-edge-weight ((graph typed-graph) (n1 integer) (n2 integer)
&key edge-type (delta 1))
(let ((matrix (gethash edge-type (matrix graph))))
(incf-sarray matrix (list n1 n2) delta)))
(defmethod incf-edge-weight ((graph typed-graph) n1 n2 &key edge-type delta)
(let ((id1 (lookup-node graph n1))
(id2 (lookup-node graph n2)))
(when (and id1 id2)
(incf-edge-weight graph
id1
id2
:edge-type edge-type
:delta delta))))
(defmethod decf-edge-weight ((graph typed-graph) (n1 integer) (n2 integer)
&key edge-type (delta 1))
(let ((matrix (gethash edge-type (matrix graph))))
(decf-sarray matrix (list n1 n2) delta)))
(defmethod decf-edge-weight ((graph typed-graph) n1 n2 &key edge-type delta)
(decf-edge-weight graph
(gethash n1 (nodes graph))
(gethash n2 (nodes graph))
:edge-type edge-type
:delta delta))
(defmethod random-edge ((graph typed-graph) &optional edge-type)
(let ((n1 nil) (n2 nil) (w 0) (edge-types (edge-types graph)))
(loop until (> w 0) do
(let* ((edge-type (or edge-type
(elt edge-types (random (length edge-types)))))
(matrix (gethash edge-type (matrix graph))))
(setq n1 (random (row-count matrix))
n2 (random (col-count matrix))
w (saref matrix n1 n2))))
(list n1 n2)))
(defmethod swap-edges ((graph typed-graph) e1 e2)
(unless (and (= 3 (length e1)) (= 3 (length e2)))
(error "Edges must be typed in a typed graph."))
(apply #'delete-edge (cons graph e1))
(apply #'delete-edge (cons graph e2))
(add-edge graph (first e1) (first e2) :edge-type (third e1))
(add-edge graph (second e1) (second e2) :edge-type (third e2)))
(defmethod reverse-edge ((graph typed-graph) n1 n2 &optional edge-type)
(let ((weight (edge-weight graph n1 n2 edge-type)))
(delete-edge graph n1 n2 edge-type)
(add-edge graph n2 n1 :edge-type edge-type :weight weight)))
(defmethod reverse-all-edges ((graph typed-graph))
(dolist (edge (list-edges graph :nodes-as-ids t))
(let ((weight (edge-weight graph (first edge) (second edge) (third edge))))
(delete-edge graph (first edge) (second edge) (third edge))
(add-edge graph (second edge) (first edge)
:weight weight
:edge-type (third edge))))
graph)