Add graph comparison

Julien LepillerThu Apr 02 03:19:49+0200 2020

e8152ef

Add graph comparison

rdf/rdf.scm

1616
;;;; 
1717
1818
(define-module (rdf rdf)
19+
  #:use-module (srfi srfi-1)
1920
  #:use-module (srfi srfi-9)
2021
  #:use-module (ice-9 match)
2122
  #:export (rdf-datatype

5253
            rdf-graph?
5354
5455
            merge-graphs
55-
            ))
56+
            rdf-isomorphic?))
5657
5758
;; From the specification:
5859
;;   Datatypes are used with RDF literals to represent values such as strings,

212213
  "Merge two graphs g1 and g2.  This is the same as append, but we need to make
213214
sure we rename blank nodes, or some nodes will be merged when they shouldn't."
214215
  (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)))))))