Add graph comparison
rdf/rdf.scm
16 | 16 | ;;;; | |
17 | 17 | ||
18 | 18 | (define-module (rdf rdf) | |
19 | + | #:use-module (srfi srfi-1) | |
19 | 20 | #:use-module (srfi srfi-9) | |
20 | 21 | #:use-module (ice-9 match) | |
21 | 22 | #:export (rdf-datatype | |
… | |||
52 | 53 | rdf-graph? | |
53 | 54 | ||
54 | 55 | merge-graphs | |
55 | - | )) | |
56 | + | rdf-isomorphic?)) | |
56 | 57 | ||
57 | 58 | ;; From the specification: | |
58 | 59 | ;; Datatypes are used with RDF literals to represent values such as strings, | |
… | |||
212 | 213 | "Merge two graphs g1 and g2. This is the same as append, but we need to make | |
213 | 214 | sure we rename blank nodes, or some nodes will be merged when they shouldn't." | |
214 | 215 | (append g1 (rename-blanks g2 (last-blank g1)))) | |
216 | + | ||
217 | + | ;; Next, a predicate on isomorphisms between two graphs. Two graphs are isomorphic | |
218 | + | ;; when each triple has a corresponding triple in the other graph. | |
219 | + | ;; | |
220 | + | ;; To take blank nodes into account, there needs to be a mapping from blank nodes | |
221 | + | ;; of the first graph to blank nodes of the other graph in order to prove | |
222 | + | ;; isomorphism. | |
223 | + | ;; | |
224 | + | ;; First, we compare the two graphs and find possible constraints on that mapping. | |
225 | + | ;; for instance, if one graph has (_:1, p, o) and the other (_:2, p, o), then | |
226 | + | ;; a possible constraint is that _:1 maps to _:2. If the other graph also has | |
227 | + | ;; (_:3, p, o) then maybe _:1 actually maps to _:3. | |
228 | + | ;; | |
229 | + | ;; Constraints are either "none" (no constraint), "equiv" (a mapping between two | |
230 | + | ;; blank node identifiers), "or" (a disjunction) or "and" (a conjunction). | |
231 | + | ;; By comparing the triples of the first graph, we create an conjunction between | |
232 | + | ;; the constraints collected from each triple. The constraints of a triple is | |
233 | + | ;; a disjunction between every case where it matches a triple from the other graph. | |
234 | + | ;; That creates zero, one or two constraints (depending on the number of blank | |
235 | + | ;; nodes). | |
236 | + | ;; | |
237 | + | ;; These constraints are transformed in a normal form, as a list of lists of | |
238 | + | ;; conjunctions. Each list is a candidate mapping. sat? is used to evaluate the | |
239 | + | ;; candidate mapping and ensure it is an isomorphism between the two sets of | |
240 | + | ;; blank nodes. For every sat? equivalences, we check that the mapping actually | |
241 | + | ;; maps triples of g1 to triples of g2, and its reverse mapping maps triples of | |
242 | + | ;; g2 to triples of g1. Whenever one mapping works, the two graphs are equivalent. | |
243 | + | ;; If no mapping works, the two graphs are not equivalent. | |
244 | + | ||
245 | + | (define (sat? equivalences) | |
246 | + | "Return whether the set of equivalences satisfies the condition that it represents | |
247 | + | an isomorphism between two blank node sets: for every equality, check that the | |
248 | + | first component is always associated to the same second component, and that the | |
249 | + | second component is always associated with the first." | |
250 | + | (match equivalences | |
251 | + | ('() #t) | |
252 | + | (((first . second) equivalences ...) | |
253 | + | (if (and (null? (filter | |
254 | + | (lambda (eq) | |
255 | + | (and (equal? (car eq) first) | |
256 | + | (not (equal? (cdr eq) second)))) | |
257 | + | equivalences)) | |
258 | + | (null? (filter | |
259 | + | (lambda (eq) | |
260 | + | (and (not (equal? (car eq) first)) | |
261 | + | (equal? (cdr eq) second))) | |
262 | + | equivalences))) | |
263 | + | (sat? equivalences) | |
264 | + | #f)))) | |
265 | + | ||
266 | + | (define (merge-joins l1 l2) | |
267 | + | (match l1 | |
268 | + | ('() l2) | |
269 | + | ((e1 l1 ...) | |
270 | + | (merge-joins l1 (map (lambda (e2) (append e1 e2)) l2))))) | |
271 | + | ||
272 | + | (define (to-disjunctions constraints) | |
273 | + | (match constraints | |
274 | + | (('equiv b1 b2) (list (list (cons b1 b2)))) | |
275 | + | ('none (list (list))) | |
276 | + | (('or e1 e2) (append (to-disjunctions e1) (to-disjunctions e2))) | |
277 | + | (('and e1 e2) | |
278 | + | (merge-joins (to-disjunctions e1) (to-disjunctions e2))))) | |
279 | + | ||
280 | + | (define (generate-triple-constraints t1 t2) | |
281 | + | (match t1 | |
282 | + | (($ rdf-triple s1 p1 o1) | |
283 | + | (match t2 | |
284 | + | (($ rdf-triple s2 p2 o2) | |
285 | + | (if (and (or (equal? s1 s2) (and (blank-node? s1) (blank-node? s2))) | |
286 | + | (equal? p1 p2) | |
287 | + | (or (equal? o1 o2) (and (blank-node? o1) (blank-node? o2)))) | |
288 | + | (list 'and | |
289 | + | (if (blank-node? s1) | |
290 | + | (list 'equiv s1 s2) | |
291 | + | 'none) | |
292 | + | (if (blank-node? o1) | |
293 | + | (list 'equiv o1 o2) | |
294 | + | 'none)) | |
295 | + | #f)))))) | |
296 | + | ||
297 | + | (define (generate-constraints t1 g2) | |
298 | + | (match g2 | |
299 | + | ('() 'none) | |
300 | + | ((t2 g2 ...) | |
301 | + | (let ((c (generate-triple-constraints t1 t2))) | |
302 | + | (if c | |
303 | + | (list 'or c (generate-constraints t1 g2)) | |
304 | + | (generate-constraints t1 g2)))))) | |
305 | + | ||
306 | + | (define (reverse-mapping mapping) | |
307 | + | (let loop ((mapping mapping) (result '())) | |
308 | + | (match mapping | |
309 | + | ('() result) | |
310 | + | (((first . second) mapping ...) | |
311 | + | (loop mapping (cons (cons second first) result)))))) | |
312 | + | ||
313 | + | (define (validate-mapping mapping g1 g2) | |
314 | + | (match g1 | |
315 | + | ('() #t) | |
316 | + | ((t1 g1 ...) | |
317 | + | (and (not (null? (filter | |
318 | + | (lambda (t2) | |
319 | + | (let ((s1 (rdf-triple-subject t1)) | |
320 | + | (s2 (rdf-triple-subject t2)) | |
321 | + | (p1 (rdf-triple-predicate t1)) | |
322 | + | (p2 (rdf-triple-predicate t2)) | |
323 | + | (o1 (rdf-triple-object t1)) | |
324 | + | (o2 (rdf-triple-object t2))) | |
325 | + | (and | |
326 | + | (if (blank-node? s1) | |
327 | + | (equal? (assoc-ref mapping s1) s2) | |
328 | + | (equal? s1 s2)) | |
329 | + | (equal? p1 p2) | |
330 | + | (if (blank-node? o1) | |
331 | + | (equal? (assoc-ref mapping o1) o2) | |
332 | + | (equal? o1 o2))))) | |
333 | + | g2))) | |
334 | + | (validate-mapping mapping g1 g2))))) | |
335 | + | ||
336 | + | (define (rdf-isomorphic? g1 g2) | |
337 | + | "Compare two graphs and return whether they are isomorphic." | |
338 | + | (let* ((constraints (fold (lambda (t constraints) | |
339 | + | (list 'and (generate-constraints t g2) constraints)) | |
340 | + | 'none g1)) | |
341 | + | (disjunctions (to-disjunctions constraints))) | |
342 | + | (pk 'dis disjunctions) | |
343 | + | (let loop ((disjunctions disjunctions)) | |
344 | + | (match disjunctions | |
345 | + | ('() (and (null? g1) (null? g2))) | |
346 | + | ((mapping disjunctions ...) | |
347 | + | (if (and (validate-mapping mapping g1 g2) | |
348 | + | (validate-mapping (reverse-mapping mapping) g2 g1)) | |
349 | + | #t | |
350 | + | (loop disjunctions))))))) |