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))))))) | |