gonum.org/v1/gonum@v0.14.0/graph/formats/rdf/equi_canonical_test.go (about) 1 // Copyright ©2021 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rdf 6 7 import ( 8 "fmt" 9 "io" 10 "reflect" 11 "strings" 12 "testing" 13 ) 14 15 func TestRemoveRedundantNodes(t *testing.T) { 16 tests := []struct { 17 name string 18 statements string 19 want string 20 }{ 21 { 22 name: "Example 5.1", 23 statements: ` 24 <ex:Chile> <ex:cabinet> _:b1 . 25 <ex:Chile> <ex:cabinet> _:b2 . 26 <ex:Chile> <ex:cabinet> _:b3 . 27 <ex:Chile> <ex:cabinet> _:b4 . 28 <ex:Chile> <ex:presidency> _:a1 . 29 <ex:Chile> <ex:presidency> _:a2 . 30 <ex:Chile> <ex:presidency> _:a3 . 31 <ex:Chile> <ex:presidency> _:a4 . 32 <ex:MBachelet> <ex:spouse> _:c . 33 _:a1 <ex:next> _:a2 . 34 _:a2 <ex:next> _:a3 . 35 _:a2 <ex:president> <ex:MBachelet> . 36 _:a3 <ex:next> _:a4 . 37 _:a4 <ex:president> <ex:MBachelet> . 38 _:b2 <ex:members> "23" . 39 _:b3 <ex:members> "23" . 40 `, 41 want: `<ex:Chile> <ex:cabinet> _:b3 . 42 <ex:Chile> <ex:presidency> _:a1 . 43 <ex:Chile> <ex:presidency> _:a2 . 44 <ex:Chile> <ex:presidency> _:a3 . 45 <ex:Chile> <ex:presidency> _:a4 . 46 <ex:MBachelet> <ex:spouse> _:c . 47 _:a1 <ex:next> _:a2 . 48 _:a2 <ex:next> _:a3 . 49 _:a2 <ex:president> <ex:MBachelet> . 50 _:a3 <ex:next> _:a4 . 51 _:a4 <ex:president> <ex:MBachelet> . 52 _:b3 <ex:members> "23" . 53 `, 54 }, 55 { 56 name: "Example 5.2", 57 statements: ` 58 _:a <ex:p> _:b . 59 _:a <ex:p> _:d . 60 _:c <ex:p> _:b . 61 _:c <ex:p> _:f . 62 _:e <ex:p> _:b . 63 _:e <ex:p> _:d . 64 _:e <ex:p> _:f . 65 _:e <ex:p> _:h . 66 _:g <ex:p> _:d . 67 _:g <ex:p> _:h . 68 _:i <ex:p> _:f . 69 _:i <ex:p> _:h . 70 `, 71 want: `_:e <ex:p> _:b . 72 `, 73 }, 74 } 75 76 for _, test := range tests { 77 g := parseStatements(strings.NewReader(test.statements)) 78 gWant := parseStatements(strings.NewReader(test.want)) 79 80 g = removeRedundantBnodes(g) 81 82 got := canonicalStatements(g) 83 want := canonicalStatements(gWant) 84 if got != want { 85 got = formatStatements(g) 86 t.Errorf("unexpected result for %s:\ngot: \n%s\nwant:\n%s", 87 test.name, got, test.want) 88 } 89 90 } 91 } 92 func TestFindCandidates(t *testing.T) { 93 tests := []struct { 94 name string 95 statements string 96 want string 97 wantFixed []map[string]bool 98 wantAllFixed bool 99 wantCands []map[string]map[string]bool 100 }{ 101 { 102 name: "Example 5.1", 103 statements: ` 104 <ex:Chile> <ex:cabinet> _:b1 . 105 <ex:Chile> <ex:cabinet> _:b2 . 106 <ex:Chile> <ex:cabinet> _:b3 . 107 <ex:Chile> <ex:cabinet> _:b4 . 108 <ex:Chile> <ex:presidency> _:a1 . 109 <ex:Chile> <ex:presidency> _:a2 . 110 <ex:Chile> <ex:presidency> _:a3 . 111 <ex:Chile> <ex:presidency> _:a4 . 112 <ex:MBachelet> <ex:spouse> _:c . 113 _:a1 <ex:next> _:a2 . 114 _:a2 <ex:next> _:a3 . 115 _:a2 <ex:president> <ex:MBachelet> . 116 _:a3 <ex:next> _:a4 . 117 _:a4 <ex:president> <ex:MBachelet> . 118 _:b2 <ex:members> "23" . 119 _:b3 <ex:members> "23" . 120 `, 121 want: `<ex:Chile> <ex:cabinet> _:b3 . 122 <ex:Chile> <ex:presidency> _:a1 . 123 <ex:Chile> <ex:presidency> _:a2 . 124 <ex:Chile> <ex:presidency> _:a3 . 125 <ex:Chile> <ex:presidency> _:a4 . 126 <ex:MBachelet> <ex:spouse> _:c . 127 _:a1 <ex:next> _:a2 . 128 _:a2 <ex:next> _:a3 . 129 _:a2 <ex:president> <ex:MBachelet> . 130 _:a3 <ex:next> _:a4 . 131 _:a4 <ex:president> <ex:MBachelet> . 132 _:b3 <ex:members> "23" . 133 `, 134 // Hence, in this particular case, we have managed to fix all blank 135 // nodes, and the graph is thus lean, and we need to go no further. 136 // In other cases we will look at later, however, some blank nodes 137 // may maintain multiple candidates. 138 // 139 // Note that there are two valid labellings of the graph since _:b2 140 // and _:b3 are not distinguishable. 141 wantFixed: []map[string]bool{ 142 { 143 "_:a1": true, "_:a2": true, "_:a3": true, "_:a4": true, 144 "_:b2": true, "_:c": true, 145 }, 146 { 147 "_:a1": true, "_:a2": true, "_:a3": true, "_:a4": true, 148 "_:b3": true, "_:c": true, 149 }, 150 }, 151 wantAllFixed: true, 152 wantCands: []map[string]map[string]bool{ 153 { 154 "_:a1": {"_:a1": true}, 155 "_:a2": {"_:a2": true}, 156 "_:a3": {"_:a3": true}, 157 "_:a4": {"_:a4": true}, 158 "_:b2": {"_:b2": true}, 159 "_:c": {"_:c": true}, 160 }, 161 { 162 "_:a1": {"_:a1": true}, 163 "_:a2": {"_:a2": true}, 164 "_:a3": {"_:a3": true}, 165 "_:a4": {"_:a4": true}, 166 "_:b3": {"_:b3": true}, 167 "_:c": {"_:c": true}, 168 }, 169 }, 170 }, 171 { 172 name: "Example 5.6", // This is 5.1, but simplified. 173 statements: ` 174 <ex:Chile> <ex:cabinet> _:b3 . 175 <ex:Chile> <ex:presidency> _:a1 . 176 <ex:Chile> <ex:presidency> _:a2 . 177 <ex:Chile> <ex:presidency> _:a3 . 178 <ex:Chile> <ex:presidency> _:a4 . 179 <ex:MBachelet> <ex:spouse> _:c . 180 _:a1 <ex:next> _:a2 . 181 _:a2 <ex:next> _:a3 . 182 _:a2 <ex:president> <ex:MBachelet> . 183 _:a3 <ex:next> _:a4 . 184 _:a4 <ex:president> <ex:MBachelet> . 185 _:b3 <ex:members> "23" . 186 `, 187 want: `<ex:Chile> <ex:cabinet> _:b3 . 188 <ex:Chile> <ex:presidency> _:a1 . 189 <ex:Chile> <ex:presidency> _:a2 . 190 <ex:Chile> <ex:presidency> _:a3 . 191 <ex:Chile> <ex:presidency> _:a4 . 192 <ex:MBachelet> <ex:spouse> _:c . 193 _:a1 <ex:next> _:a2 . 194 _:a2 <ex:next> _:a3 . 195 _:a2 <ex:president> <ex:MBachelet> . 196 _:a3 <ex:next> _:a4 . 197 _:a4 <ex:president> <ex:MBachelet> . 198 _:b3 <ex:members> "23" . 199 `, 200 // Hence, in this particular case, we have managed to fix all blank 201 // nodes, and the graph is thus lean, and we need to go no further. 202 // In other cases we will look at later, however, some blank nodes 203 // may maintain multiple candidates. 204 wantFixed: []map[string]bool{{ 205 "_:a1": true, "_:a2": true, "_:a3": true, "_:a4": true, 206 "_:b3": true, "_:c": true, 207 }}, 208 wantAllFixed: true, 209 wantCands: []map[string]map[string]bool{{ 210 "_:a1": {"_:a1": true}, 211 "_:a2": {"_:a2": true}, 212 "_:a3": {"_:a3": true}, 213 "_:a4": {"_:a4": true}, 214 "_:b3": {"_:b3": true}, 215 "_:c": {"_:c": true}, 216 }}, 217 }, 218 { 219 name: "Example 5.9", 220 statements: ` 221 <ex:Chile> <ex:presidency> _:a1 . 222 <ex:Chile> <ex:presidency> _:a2 . 223 <ex:Chile> <ex:presidency> _:a3 . 224 <ex:Chile> <ex:presidency> _:a4 . 225 _:a1 <ex:next> _:a2 . 226 _:a2 <ex:next> _:a3 . 227 _:a3 <ex:next> _:a4 . 228 `, 229 want: `<ex:Chile> <ex:presidency> _:a1 . 230 <ex:Chile> <ex:presidency> _:a2 . 231 <ex:Chile> <ex:presidency> _:a3 . 232 <ex:Chile> <ex:presidency> _:a4 . 233 _:a1 <ex:next> _:a2 . 234 _:a2 <ex:next> _:a3 . 235 _:a3 <ex:next> _:a4 . 236 `, 237 wantFixed: []map[string]bool{nil}, 238 wantAllFixed: true, 239 wantCands: []map[string]map[string]bool{{ 240 "_:a1": {"_:a1": true, "_:a2": true, "_:a3": true}, 241 "_:a2": {"_:a2": true, "_:a3": true}, 242 "_:a3": {"_:a2": true, "_:a3": true}, 243 "_:a4": {"_:a2": true, "_:a3": true, "_:a4": true}, 244 }}, 245 }, 246 { 247 name: "Example 5.10", 248 statements: ` 249 _:a <ex:p> _:b . 250 _:a <ex:p> _:d . 251 _:b <ex:q> _:e . 252 _:c <ex:p> _:b . 253 _:c <ex:p> _:f . 254 _:d <ex:q> _:e . 255 _:f <ex:q> _:e . 256 _:g <ex:p> _:d . 257 _:g <ex:p> _:h . 258 _:h <ex:q> _:e . 259 _:i <ex:p> _:f . 260 _:i <ex:p> _:h . 261 `, 262 want: `_:a <ex:p> _:b . 263 _:a <ex:p> _:d . 264 _:b <ex:q> _:e . 265 _:c <ex:p> _:b . 266 _:c <ex:p> _:f . 267 _:d <ex:q> _:e . 268 _:f <ex:q> _:e . 269 _:g <ex:p> _:d . 270 _:g <ex:p> _:h . 271 _:h <ex:q> _:e . 272 _:i <ex:p> _:f . 273 _:i <ex:p> _:h . 274 `, 275 wantFixed: []map[string]bool{{"_:e": true}}, 276 wantAllFixed: false, 277 wantCands: []map[string]map[string]bool{{ 278 "_:a": {"_:a": true, "_:c": true, "_:g": true, "_:i": true}, 279 "_:b": {"_:b": true, "_:d": true, "_:f": true, "_:h": true}, 280 "_:c": {"_:a": true, "_:c": true, "_:g": true, "_:i": true}, 281 "_:d": {"_:b": true, "_:d": true, "_:f": true, "_:h": true}, 282 "_:e": {"_:e": true}, 283 "_:f": {"_:b": true, "_:d": true, "_:f": true, "_:h": true}, 284 "_:g": {"_:a": true, "_:c": true, "_:g": true, "_:i": true}, 285 "_:h": {"_:b": true, "_:d": true, "_:f": true, "_:h": true}, 286 "_:i": {"_:a": true, "_:c": true, "_:g": true, "_:i": true}, 287 }}, 288 }, 289 } 290 291 for _, test := range tests[:1] { 292 g := parseStatements(strings.NewReader(test.statements)) 293 gWant := parseStatements(strings.NewReader(test.want)) 294 295 g, fixed, cands, allFixed := findCandidates(g) 296 297 got := canonicalStatements(g) 298 want := canonicalStatements(gWant) 299 if got != want { 300 got = formatStatements(g) 301 t.Errorf("unexpected result for %s:\ngot: \n%s\nwant:\n%s", 302 test.name, got, test.want) 303 } 304 305 matchedFixed := false 306 for _, wantFixed := range test.wantFixed { 307 if reflect.DeepEqual(fixed, wantFixed) { 308 matchedFixed = true 309 break 310 } 311 } 312 if !matchedFixed { 313 t.Errorf("unexpected fixed result for %s:\ngot: \n%v\nwant:\n%v", 314 test.name, fixed, test.wantFixed) 315 } 316 317 if allFixed != test.wantAllFixed { 318 t.Errorf("unexpected all-fixed result for %s:\ngot:%t\nwant:%t", 319 test.name, allFixed, test.wantAllFixed) 320 } 321 322 matchedCands := false 323 for _, wantCands := range test.wantCands { 324 if reflect.DeepEqual(cands, wantCands) { 325 matchedCands = true 326 break 327 } 328 } 329 if !matchedCands { 330 t.Errorf("unexpected candidates result for %s:\ngot: \n%v\nwant:\n%v", 331 test.name, cands, test.wantCands) 332 } 333 } 334 } 335 336 func TestLean(t *testing.T) { 337 var tests = []struct { 338 name string 339 statements string 340 want string 341 wantErr error 342 }{ 343 { 344 name: "Example 5.1", 345 statements: ` 346 <ex:Chile> <ex:cabinet> _:b1 . 347 <ex:Chile> <ex:cabinet> _:b2 . 348 <ex:Chile> <ex:cabinet> _:b3 . 349 <ex:Chile> <ex:cabinet> _:b4 . 350 <ex:Chile> <ex:presidency> _:a1 . 351 <ex:Chile> <ex:presidency> _:a2 . 352 <ex:Chile> <ex:presidency> _:a3 . 353 <ex:Chile> <ex:presidency> _:a4 . 354 <ex:MBachelet> <ex:spouse> _:c . 355 _:a1 <ex:next> _:a2 . 356 _:a2 <ex:next> _:a3 . 357 _:a2 <ex:president> <ex:MBachelet> . 358 _:a3 <ex:next> _:a4 . 359 _:a4 <ex:president> <ex:MBachelet> . 360 _:b2 <ex:members> "23" . 361 _:b3 <ex:members> "23" . 362 `, 363 want: `<ex:Chile> <ex:cabinet> _:b3 . 364 <ex:Chile> <ex:presidency> _:a1 . 365 <ex:Chile> <ex:presidency> _:a2 . 366 <ex:Chile> <ex:presidency> _:a3 . 367 <ex:Chile> <ex:presidency> _:a4 . 368 <ex:MBachelet> <ex:spouse> _:c . 369 _:a1 <ex:next> _:a2 . 370 _:a2 <ex:next> _:a3 . 371 _:a2 <ex:president> <ex:MBachelet> . 372 _:a3 <ex:next> _:a4 . 373 _:a4 <ex:president> <ex:MBachelet> . 374 _:b3 <ex:members> "23" . 375 `, 376 }, 377 { 378 name: "Example 5.6", 379 statements: ` 380 <ex:Chile> <ex:cabinet> _:b3 . 381 <ex:Chile> <ex:presidency> _:a1 . 382 <ex:Chile> <ex:presidency> _:a2 . 383 <ex:Chile> <ex:presidency> _:a3 . 384 <ex:Chile> <ex:presidency> _:a4 . 385 <ex:MBachelet> <ex:spouse> _:c . 386 _:a1 <ex:next> _:a2 . 387 _:a2 <ex:next> _:a3 . 388 _:a2 <ex:president> <ex:MBachelet> . 389 _:a3 <ex:next> _:a4 . 390 _:a4 <ex:president> <ex:MBachelet> . 391 _:b3 <ex:members> "23" . 392 `, 393 want: `<ex:Chile> <ex:cabinet> _:b3 . 394 <ex:Chile> <ex:presidency> _:a1 . 395 <ex:Chile> <ex:presidency> _:a2 . 396 <ex:Chile> <ex:presidency> _:a3 . 397 <ex:Chile> <ex:presidency> _:a4 . 398 <ex:MBachelet> <ex:spouse> _:c . 399 _:a1 <ex:next> _:a2 . 400 _:a2 <ex:next> _:a3 . 401 _:a2 <ex:president> <ex:MBachelet> . 402 _:a3 <ex:next> _:a4 . 403 _:a4 <ex:president> <ex:MBachelet> . 404 _:b3 <ex:members> "23" . 405 `, 406 }, 407 { 408 name: "Example 5.9", 409 statements: ` 410 <ex:Chile> <ex:presidency> _:a1 . 411 <ex:Chile> <ex:presidency> _:a2 . 412 <ex:Chile> <ex:presidency> _:a3 . 413 <ex:Chile> <ex:presidency> _:a4 . 414 _:a1 <ex:next> _:a2 . 415 _:a2 <ex:next> _:a3 . 416 _:a3 <ex:next> _:a4 . 417 `, 418 want: `<ex:Chile> <ex:presidency> _:a1 . 419 <ex:Chile> <ex:presidency> _:a2 . 420 <ex:Chile> <ex:presidency> _:a3 . 421 <ex:Chile> <ex:presidency> _:a4 . 422 _:a1 <ex:next> _:a2 . 423 _:a2 <ex:next> _:a3 . 424 _:a3 <ex:next> _:a4 . 425 `, 426 }, 427 { 428 name: "Example 5.10", 429 statements: ` 430 _:a <ex:p> _:b . 431 _:a <ex:p> _:d . 432 _:b <ex:q> _:e . 433 _:c <ex:p> _:b . 434 _:c <ex:p> _:f . 435 _:d <ex:q> _:e . 436 _:f <ex:q> _:e . 437 _:g <ex:p> _:d . 438 _:g <ex:p> _:h . 439 _:h <ex:q> _:e . 440 _:i <ex:p> _:f . 441 _:i <ex:p> _:h . 442 `, 443 want: `_:a <ex:p> _:b . 444 _:b <ex:q> _:e . 445 `, 446 }, 447 { 448 name: "Example 5.10 halved", 449 statements: ` 450 _:a <ex:p> _:b . 451 _:a <ex:p> _:d . 452 _:b <ex:q> _:e . 453 _:c <ex:p> _:b . 454 _:c <ex:p> _:f . 455 _:d <ex:q> _:e . 456 _:f <ex:q> _:e . 457 `, 458 want: `_:a <ex:p> _:b . 459 _:b <ex:q> _:e . 460 `, 461 }, 462 { 463 name: "Example 5.10 quartered", 464 statements: ` 465 _:a <ex:p> _:b . 466 _:a <ex:p> _:d . 467 _:b <ex:q> _:e . 468 _:d <ex:q> _:e . 469 `, 470 want: `_:a <ex:p> _:b . 471 _:b <ex:q> _:e . 472 `, 473 }, 474 } 475 476 for _, test := range tests { 477 g := parseStatements(strings.NewReader(test.statements)) 478 gWant := parseStatements(strings.NewReader(test.want)) 479 480 lean, err := Lean(g) 481 if err != test.wantErr { 482 t.Errorf("unexpected error for %v: got:%v want:%v", 483 test.name, err, test.wantErr) 484 } 485 486 got := canonicalStatements(lean) 487 want := canonicalStatements(gWant) 488 489 if got != want { 490 got = formatStatements(g) 491 t.Errorf("unexpected result for %s:\ngot: \n%s\nwant:\n%s", 492 test.name, got, test.want) 493 } 494 } 495 } 496 497 func TestJoin(t *testing.T) { 498 var tests = []struct { 499 name string 500 q string 501 statements string 502 cands map[string]map[string]bool 503 mu map[string]string 504 want []map[string]string 505 }{ 506 { 507 name: "Identity", 508 q: `_:a <ex:p> _:b .`, 509 statements: ` 510 _:a <ex:p> _:b . 511 `, 512 cands: map[string]map[string]bool{ 513 "_:a": {"_:a": true}, 514 "_:b": {"_:b": true}, 515 }, 516 mu: nil, 517 want: []map[string]string{ 518 {"_:a": "_:a", "_:b": "_:b"}, 519 }, 520 }, 521 { 522 name: "Cross identity", 523 q: `_:a <ex:p> _:b .`, 524 statements: ` 525 _:a <ex:p> _:b . 526 _:b <ex:p> _:a . 527 `, 528 cands: map[string]map[string]bool{ 529 "_:a": {"_:a": true, "_:b": true}, 530 "_:b": {"_:b": true, "_:a": true}, 531 }, 532 mu: nil, 533 want: []map[string]string{ 534 {"_:a": "_:a", "_:b": "_:b"}, 535 {"_:a": "_:b", "_:b": "_:a"}, 536 }, 537 }, 538 { 539 name: "Cross identity with restriction", 540 q: `_:a <ex:p> _:b .`, 541 statements: ` 542 _:a <ex:p> _:b . 543 _:b <ex:p> _:a . 544 `, 545 cands: map[string]map[string]bool{ 546 "_:a": {"_:a": true, "_:b": true}, 547 "_:b": {"_:b": true, "_:a": true}, 548 }, 549 mu: map[string]string{"_:a": "_:a"}, 550 want: []map[string]string{ 551 {"_:a": "_:a", "_:b": "_:b"}, 552 }, 553 }, 554 { 555 name: "Cross identity with complete restriction", 556 q: `_:a <ex:p> _:b .`, 557 statements: ` 558 _:a <ex:p> _:b . 559 _:b <ex:p> _:a . 560 `, 561 cands: map[string]map[string]bool{ 562 "_:a": {"_:a": true, "_:b": true}, 563 "_:b": {"_:b": true, "_:a": true}, 564 }, 565 mu: map[string]string{"_:a": "_:a", "_:b": "_:a"}, 566 want: nil, 567 }, 568 { 569 name: "Cross identity with extension", 570 q: `_:a <ex:p> _:b .`, 571 statements: ` 572 _:a <ex:p> _:b . 573 _:b <ex:p> _:a . 574 `, 575 cands: map[string]map[string]bool{ 576 "_:a": {"_:a": true, "_:b": true}, 577 "_:b": {"_:b": true, "_:a": true}, 578 }, 579 mu: map[string]string{"_:c": "_:a"}, 580 want: []map[string]string{ 581 {"_:a": "_:a", "_:b": "_:b", "_:c": "_:a"}, 582 {"_:a": "_:b", "_:b": "_:a", "_:c": "_:a"}, 583 }, 584 }, 585 586 { 587 name: "Loop", 588 q: `_:a <ex:p> _:a .`, 589 statements: ` 590 _:a <ex:p> _:a . 591 `, 592 cands: map[string]map[string]bool{ 593 "_:a": {"_:a": true}, 594 }, 595 mu: nil, 596 want: []map[string]string{ 597 {"_:a": "_:a"}, 598 }, 599 }, 600 { 601 name: "Cross identity loop", 602 q: `_:a <ex:p> _:a .`, 603 statements: ` 604 _:a <ex:p> _:a . 605 _:b <ex:p> _:b . 606 `, 607 cands: map[string]map[string]bool{ 608 "_:a": {"_:a": true, "_:b": true}, 609 "_:b": {"_:b": true, "_:a": true}, 610 }, 611 mu: nil, 612 want: []map[string]string{ 613 {"_:a": "_:a"}, 614 {"_:a": "_:b"}, 615 }, 616 }, 617 { 618 name: "Cross identity loop with restriction", 619 q: `_:a <ex:p> _:a .`, 620 statements: ` 621 _:a <ex:p> _:a . 622 _:b <ex:p> _:b . 623 `, 624 cands: map[string]map[string]bool{ 625 "_:a": {"_:a": true, "_:b": true}, 626 "_:b": {"_:b": true, "_:a": true}, 627 }, 628 mu: map[string]string{"_:a": "_:a"}, 629 want: []map[string]string{ 630 {"_:a": "_:a"}, 631 }, 632 }, 633 { 634 name: "Cross identity loop with complete restriction", 635 q: `_:a <ex:p> _:a .`, 636 statements: ` 637 _:a <ex:p> _:a . 638 _:b <ex:p> _:b . 639 `, 640 cands: map[string]map[string]bool{ 641 "_:a": {"_:a": true, "_:b": true}, 642 "_:b": {"_:b": true, "_:a": true}, 643 }, 644 mu: map[string]string{"_:a": "_:b", "_:b": "_:a"}, 645 want: []map[string]string{ 646 {"_:a": "_:b", "_:b": "_:a"}, 647 }, 648 }, 649 { 650 name: "Cross identity loop with extension", 651 q: `_:a <ex:p> _:a .`, 652 statements: ` 653 _:a <ex:p> _:a . 654 _:b <ex:p> _:b . 655 `, 656 cands: map[string]map[string]bool{ 657 "_:a": {"_:a": true, "_:b": true}, 658 "_:b": {"_:b": true, "_:a": true}, 659 }, 660 mu: map[string]string{"_:c": "_:a"}, 661 want: []map[string]string{ 662 {"_:a": "_:a", "_:c": "_:a"}, 663 {"_:a": "_:b", "_:c": "_:a"}, 664 }, 665 }, 666 667 { 668 name: "Example 5.9 step 1", 669 q: `_:a1 <ex:next> _:a2 .`, 670 statements: ` 671 <ex:Chile> <ex:presidency> _:a1 . 672 <ex:Chile> <ex:presidency> _:a2 . 673 <ex:Chile> <ex:presidency> _:a3 . 674 <ex:Chile> <ex:presidency> _:a4 . 675 _:a1 <ex:next> _:a2 . 676 _:a2 <ex:next> _:a3 . 677 _:a3 <ex:next> _:a4 . 678 `, 679 cands: map[string]map[string]bool{ 680 "_:a1": {"_:a1": true, "_:a2": true, "_:a3": true}, 681 "_:a2": {"_:a2": true, "_:a3": true}, 682 "_:a3": {"_:a2": true, "_:a3": true}, 683 "_:a4": {"_:a2": true, "_:a3": true, "_:a4": true}, 684 }, 685 mu: map[string]string{}, 686 want: []map[string]string{ 687 {"_:a1": "_:a1", "_:a2": "_:a2"}, 688 {"_:a1": "_:a2", "_:a2": "_:a3"}, 689 }, 690 }, 691 { 692 name: "Example 5.9 step 2", 693 q: `_:a2 <ex:next> _:a3 .`, 694 statements: ` 695 <ex:Chile> <ex:presidency> _:a1 . 696 <ex:Chile> <ex:presidency> _:a2 . 697 <ex:Chile> <ex:presidency> _:a3 . 698 <ex:Chile> <ex:presidency> _:a4 . 699 _:a1 <ex:next> _:a2 . 700 _:a2 <ex:next> _:a3 . 701 _:a3 <ex:next> _:a4 . 702 `, 703 cands: map[string]map[string]bool{ 704 "_:a1": {"_:a1": true, "_:a2": true, "_:a3": true}, 705 "_:a2": {"_:a2": true, "_:a3": true}, 706 "_:a3": {"_:a2": true, "_:a3": true}, 707 "_:a4": {"_:a2": true, "_:a3": true, "_:a4": true}, 708 }, 709 mu: map[string]string{"_:a1": "_:a2", "_:a2": "_:a3"}, 710 want: nil, 711 }, 712 { 713 name: "Example 5.9 step 3", 714 q: `_:a2 <ex:next> _:a3 .`, 715 statements: ` 716 <ex:Chile> <ex:presidency> _:a1 . 717 <ex:Chile> <ex:presidency> _:a2 . 718 <ex:Chile> <ex:presidency> _:a3 . 719 <ex:Chile> <ex:presidency> _:a4 . 720 _:a1 <ex:next> _:a2 . 721 _:a2 <ex:next> _:a3 . 722 _:a3 <ex:next> _:a4 . 723 `, 724 cands: map[string]map[string]bool{ 725 "_:a1": {"_:a1": true, "_:a2": true, "_:a3": true}, 726 "_:a2": {"_:a2": true, "_:a3": true}, 727 "_:a3": {"_:a2": true, "_:a3": true}, 728 "_:a4": {"_:a2": true, "_:a3": true, "_:a4": true}, 729 }, 730 mu: map[string]string{"_:a1": "_:a1", "_:a2": "_:a2"}, 731 want: []map[string]string{ 732 {"_:a1": "_:a1", "_:a2": "_:a2", "_:a3": "_:a3"}, 733 }, 734 }, 735 { 736 name: "Example 5.9 step 4", 737 q: `_:a3 <ex:next> _:a4 .`, 738 statements: ` 739 <ex:Chile> <ex:presidency> _:a1 . 740 <ex:Chile> <ex:presidency> _:a2 . 741 <ex:Chile> <ex:presidency> _:a3 . 742 <ex:Chile> <ex:presidency> _:a4 . 743 _:a1 <ex:next> _:a2 . 744 _:a2 <ex:next> _:a3 . 745 _:a3 <ex:next> _:a4 . 746 `, 747 cands: map[string]map[string]bool{ 748 "_:a1": {"_:a1": true, "_:a2": true, "_:a3": true}, 749 "_:a2": {"_:a2": true, "_:a3": true}, 750 "_:a3": {"_:a2": true, "_:a3": true}, 751 "_:a4": {"_:a2": true, "_:a3": true, "_:a4": true}, 752 }, 753 mu: map[string]string{"_:a1": "_:a1", "_:a2": "_:a2", "_:a3": "_:a3"}, 754 want: []map[string]string{ 755 {"_:a1": "_:a1", "_:a2": "_:a2", "_:a3": "_:a3", "_:a4": "_:a4"}, 756 }, 757 }, 758 } 759 760 for _, test := range tests { 761 q := parseStatement(strings.NewReader(test.q)) 762 g := parseStatements(strings.NewReader(test.statements)) 763 764 st := dfs{} 765 got := st.join(q, g, test.cands, test.mu) 766 if !reflect.DeepEqual(got, test.want) { 767 t.Errorf("unexpected result for %s:\ngot:\n%#v\nwant:\n%#v", 768 test.name, got, test.want) 769 } 770 771 naive := joinNaive(q, g, test.cands, []map[string]string{test.mu}) 772 if !reflect.DeepEqual(naive, test.want) { 773 t.Errorf("unexpected naive result for %s:\ngot:\n%#v\nwant:\n%#v", 774 test.name, naive, test.want) 775 } 776 777 } 778 } 779 780 // joinNaive is a direct translation of lines 47-51 of algorithm 6 in doi:10.1145/3068333. 781 func joinNaive(q *Statement, G []*Statement, cands map[string]map[string]bool, M []map[string]string) []map[string]string { 782 isLoop := q.Subject.Value == q.Object.Value 783 // Line 48: M_q ← {µ | µ(q) ∈ G} 784 var M_q []map[string]string 785 for _, s := range G { 786 // µ(q) ∈ G ↔ (µ(q_s),q_p,µ(q_o)) ∈ G 787 if q.Predicate.Value != s.Predicate.Value { 788 continue 789 } 790 // q_s = q_o ↔ µ(q_s) =_µ(q_o) 791 if isLoop && s.Subject.Value != s.Object.Value { 792 continue 793 } 794 795 var µ map[string]string 796 if isLoop { 797 µ = map[string]string{ 798 q.Subject.Value: s.Subject.Value, 799 } 800 } else { 801 µ = map[string]string{ 802 q.Subject.Value: s.Subject.Value, 803 q.Object.Value: s.Object.Value, 804 } 805 } 806 M_q = append(M_q, µ) 807 } 808 809 // Line 49: M_q' ← {µ ∈ M_q | for all b ∈ bnodes({q}), µ(b) ∈ cands[b]} 810 var M_qPrime []map[string]string 811 for _, µ := range M_q { 812 if !cands[q.Subject.Value][µ[q.Subject.Value]] { 813 continue 814 } 815 if !cands[q.Object.Value][µ[q.Object.Value]] { 816 continue 817 } 818 M_qPrime = append(M_qPrime, µ) 819 } 820 821 // Line 50: M' ← M_q' ⋈ M 822 // M₁ ⋈ M₂ = {μ₁ ∪ μ₂ | μ₁ ∈ M₁, μ₂ ∈ M₂ and μ₁, μ₂ are compatible mappings} 823 var MPrime []map[string]string 824 for _, µ := range M { 825 join: 826 for _, µ_qPrime := range M_qPrime { 827 for b, x_qPrime := range µ_qPrime { 828 if x, ok := µ[b]; ok && x != x_qPrime { 829 continue join 830 } 831 } 832 // Line 50: μ₁ ∪ μ₂ 833 for b, x := range µ { 834 µ_qPrime[b] = x 835 } 836 MPrime = append(MPrime, µ_qPrime) 837 } 838 } 839 return MPrime 840 } 841 842 func parseStatement(r io.Reader) *Statement { 843 g := parseStatements(r) 844 if len(g) != 1 { 845 panic(fmt.Sprintf("invalid statement stream length %d != 1", len(g))) 846 } 847 return g[0] 848 } 849 850 func parseStatements(r io.Reader) []*Statement { 851 var g []*Statement 852 dec := NewDecoder(r) 853 for { 854 s, err := dec.Unmarshal() 855 if err != nil { 856 if err == io.EOF { 857 break 858 } 859 panic(err) 860 } 861 g = append(g, s) 862 } 863 return g 864 } 865 866 func canonicalStatements(g []*Statement) string { 867 g, _ = URDNA2015(nil, g) 868 return formatStatements(g) 869 } 870 871 func formatStatements(g []*Statement) string { 872 var buf strings.Builder 873 for _, s := range g { 874 fmt.Fprintln(&buf, s) 875 } 876 return buf.String() 877 }