Test the magic-set transformation on an example that covers all subtransformers, namely: (1) NormaliseDatabaseTransformer (2) LabelDatabaseTransformer (3) AdornDatabaseTransformer (4) MagicSetTransformer.
380 :-
\n B(X),
\n X < max Y : { C(Y),B(Y),Y < 2 },
\n A(Z),
\n Z = sum A : { C(A),B(A),A
"
381 ">
count : { A(M),C(M) } }.
",
382 toString(*dClauses[0]));
384 "D(V) :-
\n B(V),
\n A(W),
\n W = min test1 : { C(test1),B(test1),test1 >
count : {
"
385 "C(X),A(X) } },
\n V < max test2 : { C(test2),B(test2),test2 < 2 }.
",
386 toString(*dClauses[1]));
398 TEST(Transformers, RemoveClauseRedundancies) {
399 ErrorReport errorReport;
400 DebugReport debugReport;
402 Own<TranslationUnit> tu = ParserDriver::parseTranslationUnit(
404 .decl a,
b,c(X:number)
411 a(X) :- a(X), X != 1.
418 errorReport, debugReport);
420 const auto& program = tu->getProgram();
422 // Invoking the `RemoveRelationCopiesTransformer` to create some extra redundancy
423 // In particular: The relation `c` will be replaced with `b` throughout, creating
424 // the clause b(x) :- b(x).
425 mk<RemoveRelationCopiesTransformer>()->apply(*tu);
426 EXPECT_EQ(nullptr, getRelation(program, "c
"));
427 auto bIntermediateClauses = getClauses(program, "b");
428 EXPECT_EQ(2, bIntermediateClauses.size());
429 EXPECT_EQ("b(1).
", toString(*bIntermediateClauses[0]));
430 EXPECT_EQ("b(X) :- \
n b(X).
", toString(*bIntermediateClauses[1]));
432 // Attempt to minimise the program
433 mk<MinimiseProgramTransformer>()->apply(*tu);
434 EXPECT_EQ(3, program.getRelations().size());
436 auto aClauses = getClauses(program, "a
");
437 EXPECT_EQ(2, aClauses.size());
438 EXPECT_EQ("a(0).
", toString(*aClauses[0]));
439 EXPECT_EQ("a(X) :- \
n b(X).
", toString(*aClauses[1]));
441 auto bClauses = getClauses(program, "b");
442 EXPECT_EQ(1, bClauses.size());
443 EXPECT_EQ("b(1).
", toString(*bClauses[0]));
445 auto qClauses = getClauses(program, "q");
446 EXPECT_EQ(1, qClauses.size());
447 EXPECT_EQ("q(X) :- \
n a(X).
", toString(*qClauses[0]));
457 TEST(Transformers, MagicSetComprehensive) {
461 Own<TranslationUnit> tu = ParserDriver::parseTranslationUnit(
464 .decl BaseOne(X:number) magic
465 .decl BaseTwo(X:number) magic
466 .
input BaseOne, BaseTwo
469 .decl A(X:number) magic
470 .decl B(X:number) magic
472 A(X) :- BaseOne(X), B(X).
473 B(X) :- BaseTwo(X), A(X).
476 .decl C(X:number) magic
477 C(X) :- BaseTwo(X), A(X), B(X), X != 1.
480 .decl R(X:number) magic
481 R(X) :- BaseTwo(X), A(X), B(X), X != 0.
484 .decl D(X:number) magic
485 D(X) :- BaseOne(X), A(X), !C(X), !R(X).
488 .decl Query(X:number) magic
490 Query(X) :- BaseOne(X), D(X), A(X).
494 auto& program = tu->getProgram();
497 auto mappifyRelations = [&](const Program& program) {
498 std::map<std::string, std::multiset<std::string>> result;
499 for (const auto* rel : program.getRelations()) {
500 std::multiset<std::string> clauseStrings;
501 auto relName = rel->getQualifiedName();
502 for (const auto* clause : getClauses(program, rel->getQualifiedName())) {
503 clauseStrings.insert(toString(*clause));
505 result[toString(relName)] = clauseStrings;
509 auto checkRelMapEq = [&](const std::map<std::string, std::multiset<std::string>> left,
510 const std::map<std::string, std::multiset<std::string>> right) {
511 EXPECT_EQ(left.size(), right.size());
512 for (const auto& [name, clauses] : left) {
513 EXPECT_TRUE(contains(right, name));
514 EXPECT_EQ(clauses, right.at(name));
518 /* Stage 1: Database normalisation */
519 // Constants should now be extracted from the inequality constraints
520 mk<MagicSetTransformer::NormaliseDatabaseTransformer>()->apply(*tu);
521 const auto relations1 = program.getRelations();
522 EXPECT_EQ(8, program.getRelations().size());
523 EXPECT_EQ(7, program.getClauses().size());
525 auto expectedNormalisation = std::map<std::string, std::multiset<std::string>>({
528 {"A
", {"A(X) :- \
n BaseOne(X).
", "A(X) :- \
n BaseOne(X),\
n B(X).
"}},
529 {"B
", {"B(X) :- \
n BaseTwo(X),\
n A(X).
"}},
530 {"C
", {"C(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n @abdul0 = 1.
"}},
531 {"R
", {"R(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n @abdul0 = 0.
"}},
532 {"D
", {"D(X) :- \
n BaseOne(X),\
n A(X),\
n !C(X),\
n !R(X).
"}},
533 {"Query
", {"Query(X) :- \
n BaseOne(X),\
n D(X),\
n A(X).
"}},
535 checkRelMapEq(expectedNormalisation, mappifyRelations(program));
537 /* Stage 2: Database negative and positive labelling, keeping only the relevant ones */
538 /* Stage 2.1: Negative labelling */
539 mk<MagicSetTransformer::LabelDatabaseTransformer::NegativeLabellingTransformer>()->apply(*tu);
540 EXPECT_EQ(14, program.getRelations().size());
541 EXPECT_EQ(14, program.getClauses().size());
543 auto expectedNegLabelling = std::map<std::string, std::multiset<std::string>>({
544 // Original strata - neglabels should appear on all negated appearances of relations
547 {"A
", {"A(X) :- \
n BaseOne(X).
", "A(X) :- \
n BaseOne(X),\
n B(X).
"}},
548 {"B
", {"B(X) :- \
n BaseTwo(X),\
n A(X).
"}},
549 {"C
", {"C(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n @abdul0 = 1.
"}},
550 {"R
", {"R(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n @abdul0 = 0.
"}},
551 {"D
", {"D(X) :- \
n BaseOne(X),\
n A(X),\
n !@neglabel.C(X),\
n !@neglabel.R(X).
"}},
552 {"Query
", {"Query(X) :- \
n BaseOne(X),\
n D(X),\
n A(X).
"}},
554 // Neglaelled strata - relations should be copied and neglabelled one stratum at a time
555 {"@neglabel.A
", {"@neglabel.A(X) :- \
n BaseOne(X).
",
556 "@neglabel.A(X) :- \
n BaseOne(X),\
n @neglabel.B(X).
"}},
557 {"@neglabel.B
", {"@neglabel.B(X) :- \
n BaseTwo(X),\
n @neglabel.A(X).
"}},
558 {"@neglabel.C
", {"@neglabel.C(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n "
560 {"@neglabel.R
", {"@neglabel.R(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n "
562 {"@neglabel.D
", {"@neglabel.D(X) :- \
n BaseOne(X),\
n A(X),\
n !@neglabel.C(X),\
n "
563 "!@neglabel.R(X).
"}},
564 {"@neglabel.Query
", {"@neglabel.Query(X) :- \
n BaseOne(X),\
n D(X),\
n A(X).
"}},
566 checkRelMapEq(expectedNegLabelling, mappifyRelations(program));
568 /* Stage 2.2: Positive labelling */
569 mk<MagicSetTransformer::LabelDatabaseTransformer::PositiveLabellingTransformer>()->apply(*tu);
570 EXPECT_EQ(33, program.getRelations().size());
571 EXPECT_EQ(27, program.getClauses().size());
573 auto expectedPosLabelling = std::map<std::string, std::multiset<std::string>>({
574 // Original strata should remain the same
577 {"A
", {"A(X) :- \
n BaseOne(X).
", "A(X) :- \
n BaseOne(X),\
n B(X).
"}},
578 {"B
", {"B(X) :- \
n BaseTwo(X),\
n A(X).
"}},
579 {"C
", {"C(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n @abdul0 = 1.
"}},
580 {"R
", {"R(X) :- \
n BaseTwo(X),\
n A(X),\
n B(X),\
n X != @abdul0,\
n @abdul0 = 0.
"}},
581 {"D
", {"D(X) :- \
n BaseOne(X),\
n A(X),\
n !@neglabel.C(X),\
n !@neglabel.R(X).
"}},
582 {"Query
", {"Query(X) :- \
n BaseOne(X),\
n D(X),\
n A(X).
"}},
584 // Poslabelled + neglabelled strata - poslabel all positive derived literals
585 {"@neglabel.A
", {"@neglabel.A(X) :- \
n BaseOne(X).
",
586 "@neglabel.A(X) :- \
n BaseOne(X),\
n @neglabel.B(X).
"}},
587 {"@neglabel.B
", {"@neglabel.B(X) :- \
n BaseTwo(X),\
n @neglabel.A(X).
"}},
588 {"@neglabel.C
", {"@neglabel.C(X) :- \
n BaseTwo(X),\
n @poscopy_1.A(X),\
n @poscopy_1.B(X),\
n "
591 {"@neglabel.R
", {"@neglabel.R(X) :- \
n BaseTwo(X),\
n @poscopy_2.A(X),\
n @poscopy_2.B(X),\
n "
595 {"@neglabel.D(X) :- \
n BaseOne(X),\
n @poscopy_3.A(X),\
n !@neglabel.C(X),\
n "
596 "!@neglabel.R(X).
"}},
598 {"@neglabel.Query(X) :- \
n BaseOne(X),\
n @poscopy_1.D(X),\
n @poscopy_4.A(X).
"}},
600 // Copies of input stratum
601 {"@poscopy_1.BaseOne
", {}},
602 {"@poscopy_1.BaseTwo
", {}},
603 {"@poscopy_2.BaseOne
", {}},
604 {"@poscopy_2.BaseTwo
", {}},
605 {"@poscopy_3.BaseOne
", {}},
606 {"@poscopy_3.BaseTwo
", {}},
607 {"@poscopy_4.BaseOne
", {}},
608 {"@poscopy_4.BaseTwo
", {}},
609 {"@poscopy_5.BaseOne
", {}},
610 {"@poscopy_5.BaseTwo
", {}},
612 // Copies of {A,B} stratum
613 {"@poscopy_1.A
", {"@poscopy_1.A(X) :- \
n BaseOne(X).
",
614 "@poscopy_1.A(X) :- \
n BaseOne(X),\
n @poscopy_1.B(X).
"}},
615 {"@poscopy_1.B
", {"@poscopy_1.B(X) :- \
n BaseTwo(X),\
n @poscopy_1.A(X).
"}},
616 {"@poscopy_2.A
", {"@poscopy_2.A(X) :- \
n BaseOne(X).
",
617 "@poscopy_2.A(X) :- \
n BaseOne(X),\
n @poscopy_2.B(X).
"}},
618 {"@poscopy_2.B
", {"@poscopy_2.B(X) :- \
n BaseTwo(X),\
n @poscopy_2.A(X).
"}},
619 {"@poscopy_3.A
", {"@poscopy_3.A(X) :- \
n BaseOne(X).
",
620 "@poscopy_3.A(X) :- \
n BaseOne(X),\
n @poscopy_3.B(X).
"}},
621 {"@poscopy_3.B
", {"@poscopy_3.B(X) :- \
n BaseTwo(X),\
n @poscopy_3.A(X).
"}},
622 {"@poscopy_4.A
", {"@poscopy_4.A(X) :- \
n BaseOne(X).
",
623 "@poscopy_4.A(X) :- \
n BaseOne(X),\
n @poscopy_4.B(X).
"}},
624 {"@poscopy_4.B
", {"@poscopy_4.B(X) :- \
n BaseTwo(X),\
n @poscopy_4.A(X).
"}},
626 // Copies of {D} stratum
627 {"@poscopy_1.D
", {"@poscopy_1.D(X) :- \
n BaseOne(X),\
n @poscopy_4.A(X),\
n "
628 "!@neglabel.C(X),\
n !@neglabel.R(X).
"}},
630 checkRelMapEq(expectedPosLabelling, mappifyRelations(program));
632 /* Stage 2.3: Remove unnecessary labelled relations */
633 mk<RemoveRedundantRelationsTransformer>()->apply(*tu);
634 EXPECT_EQ(12, program.getRelations().size());
635 EXPECT_EQ(13, program.getClauses().size());
637 auto expectedFullLabelling = std::map<std::string, std::multiset<std::string>>({
641 {"A
", {"A(X) :- \
n BaseOne(X).
", "A(X) :- \
n BaseOne(X),\
n B(X).
"}},
642 {"B
", {"B(X) :- \
n BaseTwo(X),\
n A(X).
"}},
643 {"D
", {"D(X) :- \
n BaseOne(X),\
n A(X),\
n !@neglabel.C(X),\
n !@neglabel.R(X).
"}},
644 {"Query
", {"Query(X) :- \
n BaseOne(X),\
n D(X),\
n A(X).
"}},
646 // Neglabelled strata
647 {"@neglabel.C
", {"@neglabel.C(X) :- \
n BaseTwo(X),\
n @poscopy_1.A(X),\
n @poscopy_1.B(X),\
n "
650 {"@neglabel.R
", {"@neglabel.R(X) :- \
n BaseTwo(X),\
n @poscopy_2.A(X),\
n @poscopy_2.B(X),\
n "
654 // Poslabelled strata
655 {"@poscopy_1.A
", {"@poscopy_1.A(X) :- \
n BaseOne(X).
",
656 "@poscopy_1.A(X) :- \
n BaseOne(X),\
n @poscopy_1.B(X).
"}},
657 {"@poscopy_1.B
", {"@poscopy_1.B(X) :- \
n BaseTwo(X),\
n @poscopy_1.A(X).
"}},
658 {"@poscopy_2.A
", {"@poscopy_2.A(X) :- \
n BaseOne(X).
",
659 "@poscopy_2.A(X) :- \
n BaseOne(X),\
n @poscopy_2.B(X).
"}},
660 {"@poscopy_2.B
", {"@poscopy_2.B(X) :- \
n BaseTwo(X),\
n @poscopy_2.A(X).
"}},
662 checkRelMapEq(expectedFullLabelling, mappifyRelations(program));
664 /* Stage 3: Database adornment */
665 /* Stage 3.1: Adornment algorithm */
666 mk<MagicSetTransformer::AdornDatabaseTransformer>()->apply(*tu);