kibi-core 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/schema/entities.pl +22 -0
- package/schema/relationships.pl +14 -0
- package/schema/validation.pl +45 -1
- package/src/checks.pl +1850 -4
- package/src/discovery.pl +214 -3
- package/src/intervals.pl +175 -0
- package/src/kb.pl +76 -29
- package/src/logic_ir.pl +679 -65
- package/src/requirement_proof.pl +60 -6
- package/src/rule_registry.pl +77 -3
- package/src/semantic_quality.pl +151 -3
- package/src/status.pl +15 -1
package/src/logic_ir.pl
CHANGED
|
@@ -10,15 +10,21 @@
|
|
|
10
10
|
logic_rule_semantic_key/2,
|
|
11
11
|
logic_derive/2,
|
|
12
12
|
logic_rule_conflict/3,
|
|
13
|
+
logic_rule_conflict/4,
|
|
13
14
|
logic_rule_conflict_witness/3,
|
|
15
|
+
logic_rule_missing_keys/4,
|
|
16
|
+
functional_predicate_declarations/1,
|
|
14
17
|
logic_atom_signature/2,
|
|
15
|
-
logic_rules_stratified/1
|
|
18
|
+
logic_rules_stratified/1,
|
|
19
|
+
logic_rule_fold_exceptions/2,
|
|
20
|
+
logic_rule_property_form/2
|
|
16
21
|
]).
|
|
17
22
|
|
|
18
23
|
:- use_module(library(http/json)).
|
|
19
24
|
:- use_module(library(aggregate)).
|
|
20
25
|
:- use_module(library(clpfd)).
|
|
21
|
-
:- use_module('
|
|
26
|
+
:- use_module('intervals.pl', [numeric_constraints_satisfiable/2, numeric_constraint_entailed/3, numeric_constraint_holds/3]).
|
|
27
|
+
:- use_module('kb.pl', [kb_entity/3, predicate_fact/5, predicate_schema_keys/4]).
|
|
22
28
|
|
|
23
29
|
%% logic_rule_from_props(+Props, -Rule)
|
|
24
30
|
% Props is an entity property list from a fact_kind=rule fact.
|
|
@@ -364,9 +370,14 @@ logic_rule_fact(FactId, Rule) :-
|
|
|
364
370
|
% The JSON decoder keeps variable names as data terms for validation and
|
|
365
371
|
% rendering. Evaluation reifies those names into fresh Prolog variables so
|
|
366
372
|
% ordinary unification can be used without ever evaluating caller source.
|
|
367
|
-
materialize_rule(
|
|
373
|
+
materialize_rule(Rule, Materialized) :-
|
|
374
|
+
materialize_rule_environment(Rule, Materialized, _Environment).
|
|
375
|
+
|
|
376
|
+
% Environment maps each declared variable name to its fresh Prolog variable.
|
|
377
|
+
materialize_rule_environment(
|
|
368
378
|
rule(Kind, Modality, Head, Body, Exceptions, Scope, From, To, Schema, Variables),
|
|
369
|
-
rule(Kind, Modality, MaterialHead, MaterialBody, MaterialExceptions, Scope, From, To, Schema, Variables)
|
|
379
|
+
rule(Kind, Modality, MaterialHead, MaterialBody, MaterialExceptions, Scope, From, To, Schema, Variables),
|
|
380
|
+
Environment
|
|
370
381
|
) :-
|
|
371
382
|
variable_environment(Variables, Environment),
|
|
372
383
|
materialize_head(Head, Environment, MaterialHead),
|
|
@@ -487,17 +498,670 @@ temporal_end(timestamp(Value), Value).
|
|
|
487
498
|
temporal_end(interval(_, End), End).
|
|
488
499
|
|
|
489
500
|
%% logic_rule_conflict(+RuleA, +RuleB, -Status)
|
|
490
|
-
%
|
|
491
|
-
%
|
|
492
|
-
%
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
+
% implements REQ-kibi-truthful-consistency
|
|
502
|
+
% Three-valued comparison of two opposing rules.
|
|
503
|
+
%
|
|
504
|
+
% contradiction neither rule carries an exception, and either the rules
|
|
505
|
+
% are variants of each other or every instance of one rule's
|
|
506
|
+
% body is an instance of the other's; in both cases the
|
|
507
|
+
% shared body must provably have an instance (every
|
|
508
|
+
% comparison translated exactly, the functional closure
|
|
509
|
+
% consistent, no negation, disjunction, count or temporal
|
|
510
|
+
% relation, and the constraints satisfiable with integer
|
|
511
|
+
% bounds for every variable any declaration types as int),
|
|
512
|
+
% so the opposing modalities are certain to collide.
|
|
513
|
+
% Variant bodies that cannot hold are disjoint; variant
|
|
514
|
+
% bodies the fragment cannot decide stay unresolved.
|
|
515
|
+
% disjoint the heads cannot denote the same action, the scopes or
|
|
516
|
+
% validity windows do not intersect, the bodies provably
|
|
517
|
+
% cannot hold together, or an exception of one rule is
|
|
518
|
+
% entailed by the other rule's body.
|
|
519
|
+
% unresolved the rules may overlap but the fragment cannot decide it.
|
|
520
|
+
%
|
|
521
|
+
% Rules are materialized before comparison: IR variables are data terms such
|
|
522
|
+
% as var('T', money) and must never be treated as ground values. Body atoms
|
|
523
|
+
% of the two rules are identified only through a declared functional
|
|
524
|
+
% dependency (a predicate_schema with key_arguments): two atoms of such a
|
|
525
|
+
% predicate whose key arguments are identical must agree on every other
|
|
526
|
+
% argument. Without a declaration a predicate is multivalued, so
|
|
527
|
+
% final_total(C, X), X > 0 and final_total(C, Y), Y =< 0 may both hold and the
|
|
528
|
+
% pair stays unresolved. A variable shared by both rules counts as integral
|
|
529
|
+
% for a disjoint verdict only when every declaration of it is int or integer.
|
|
530
|
+
% These assumptions are documented in docs/inference-rules.md.
|
|
531
|
+
logic_rule_conflict(RuleA, RuleB, Status) :-
|
|
532
|
+
functional_predicate_declarations(Functional),
|
|
533
|
+
logic_rule_conflict(RuleA, RuleB, Functional, Status).
|
|
534
|
+
|
|
535
|
+
%% logic_rule_conflict(+RuleA, +RuleB, +Functional, -Status)
|
|
536
|
+
% Functional is a list of functional(Namespace, Name, Arity, KeyPositions)
|
|
537
|
+
% declarations; logic_rule_conflict/3 reads them from predicate_schema facts.
|
|
538
|
+
logic_rule_conflict(RuleA, RuleB, Functional, Status) :-
|
|
539
|
+
( catch(rule_pair_status(RuleA, RuleB, Functional, Status0), _, Status0 = unresolved)
|
|
540
|
+
-> Status = Status0
|
|
541
|
+
; Status = disjoint
|
|
542
|
+
).
|
|
543
|
+
|
|
544
|
+
functional_predicate_declarations(Functional) :-
|
|
545
|
+
catch(
|
|
546
|
+
findall(functional(Namespace, Name, Arity, Keys),
|
|
547
|
+
predicate_schema_keys(Namespace, Name, Arity, Keys),
|
|
548
|
+
Functional0),
|
|
549
|
+
_,
|
|
550
|
+
Functional0 = []
|
|
551
|
+
),
|
|
552
|
+
sort(Functional0, Functional).
|
|
553
|
+
|
|
554
|
+
%% logic_rule_missing_keys(+RuleA, +RuleB, +Functional, -Missing)
|
|
555
|
+
% implements REQ-kibi-truthful-consistency
|
|
556
|
+
% For an opposing pair that is unresolved under the Functional declarations,
|
|
557
|
+
% the plain-conjunction body predicates without a key_arguments declaration
|
|
558
|
+
% whose declaration would decide the pair. Missing is a sorted list of
|
|
559
|
+
% missing_keys(Namespace, Name, Arity, KeySets): KeySets are the candidate
|
|
560
|
+
% key-position sets (non-empty proper subsets of the argument positions)
|
|
561
|
+
% that turn the pair into contradiction or disjoint when that one predicate
|
|
562
|
+
% is declared. When no single declaration decides the pair, declaring every
|
|
563
|
+
% such predicate keyed on all but its last argument (the value-last shape) is
|
|
564
|
+
% tried as well. Missing is empty when the pair is already decided or stays
|
|
565
|
+
% unresolved for another reason (an untranslatable comparison, negation,
|
|
566
|
+
% disjunction, counts, or genuinely overlapping bodies).
|
|
567
|
+
logic_rule_missing_keys(RuleA, RuleB, Functional, Missing) :-
|
|
568
|
+
logic_rule_conflict(RuleA, RuleB, Functional, Status),
|
|
569
|
+
( Status == unresolved
|
|
570
|
+
-> rule_body_predicates(RuleA, PredicatesA),
|
|
571
|
+
rule_body_predicates(RuleB, PredicatesB),
|
|
572
|
+
append(PredicatesA, PredicatesB, Predicates0),
|
|
573
|
+
sort(Predicates0, Predicates),
|
|
574
|
+
exclude(declared_functional(Functional), Predicates, Undeclared),
|
|
575
|
+
convlist(deciding_key_sets(RuleA, RuleB, Functional), Undeclared, Single),
|
|
576
|
+
( Single \== []
|
|
577
|
+
-> Missing = Single
|
|
578
|
+
; value_last_declarations(Undeclared, Declarations),
|
|
579
|
+
Declarations \== [],
|
|
580
|
+
append(Declarations, Functional, Assumed),
|
|
581
|
+
logic_rule_conflict(RuleA, RuleB, Assumed, AssumedStatus),
|
|
582
|
+
AssumedStatus \== unresolved
|
|
583
|
+
-> findall(missing_keys(Namespace, Name, Arity, [Keys]),
|
|
584
|
+
member(functional(Namespace, Name, Arity, Keys), Declarations),
|
|
585
|
+
Missing)
|
|
586
|
+
; Missing = []
|
|
587
|
+
)
|
|
588
|
+
; Missing = []
|
|
589
|
+
).
|
|
590
|
+
|
|
591
|
+
rule_body_predicates(rule(_, _, _, Body, _, _, _, _, _, _), Predicates) :-
|
|
592
|
+
expression_parts(Body, parts(Atoms, _, _)),
|
|
593
|
+
findall(predicate(Namespace, Name, Arity),
|
|
594
|
+
( member(atom(Namespace, Name, Args, _, _), Atoms), length(Args, Arity), Arity >= 2 ),
|
|
595
|
+
Predicates).
|
|
596
|
+
|
|
597
|
+
declared_functional(Functional, predicate(Namespace, Name, Arity)) :-
|
|
598
|
+
memberchk(functional(Namespace, Name, Arity, _), Functional).
|
|
599
|
+
|
|
600
|
+
deciding_key_sets(RuleA, RuleB, Functional, predicate(Namespace, Name, Arity),
|
|
601
|
+
missing_keys(Namespace, Name, Arity, KeySets)) :-
|
|
602
|
+
Arity =< 6,
|
|
603
|
+
findall(Keys,
|
|
604
|
+
( candidate_key_set(Arity, Keys),
|
|
605
|
+
logic_rule_conflict(RuleA, RuleB, [functional(Namespace, Name, Arity, Keys)|Functional], Status),
|
|
606
|
+
Status \== unresolved
|
|
607
|
+
),
|
|
608
|
+
KeySets),
|
|
609
|
+
KeySets \== [].
|
|
610
|
+
|
|
611
|
+
% Non-empty proper subsets of 1..Arity, smallest first.
|
|
612
|
+
candidate_key_set(Arity, Keys) :-
|
|
613
|
+
numlist(1, Arity, Positions),
|
|
614
|
+
Max is Arity - 1,
|
|
615
|
+
between(1, Max, Size),
|
|
616
|
+
length(Keys, Size),
|
|
617
|
+
ordered_subset(Keys, Positions).
|
|
618
|
+
|
|
619
|
+
ordered_subset([], _).
|
|
620
|
+
ordered_subset([Position|Rest], [Position|Positions]) :-
|
|
621
|
+
ordered_subset(Rest, Positions).
|
|
622
|
+
ordered_subset(Subset, [_|Positions]) :-
|
|
623
|
+
Subset = [_|_],
|
|
624
|
+
ordered_subset(Subset, Positions).
|
|
625
|
+
|
|
626
|
+
value_last_declarations(Predicates, Declarations) :-
|
|
627
|
+
findall(functional(Namespace, Name, Arity, Keys),
|
|
628
|
+
( member(predicate(Namespace, Name, Arity), Predicates),
|
|
629
|
+
Last is Arity - 1,
|
|
630
|
+
numlist(1, Last, Keys)
|
|
631
|
+
),
|
|
632
|
+
Declarations).
|
|
633
|
+
|
|
634
|
+
% Fails only when the rules are certainly disjoint: the modalities do not
|
|
635
|
+
% oppose, the contexts do not intersect, or the heads do not unify.
|
|
636
|
+
rule_pair_status(RuleA00, RuleB00, Functional, Status) :-
|
|
637
|
+
logic_rule_fold_exceptions(RuleA00, RuleA0),
|
|
638
|
+
logic_rule_fold_exceptions(RuleB00, RuleB0),
|
|
639
|
+
RuleA0 = rule(_, ModalityA, _, _, _, ScopeA, FromA, ToA, _, VariablesA),
|
|
640
|
+
RuleB0 = rule(_, ModalityB, _, _, _, ScopeB, FromB, ToB, _, VariablesB),
|
|
641
|
+
conflicting_modality(ModalityA, ModalityB),
|
|
642
|
+
rule_context_compatible(ScopeA, FromA, ToA, ScopeB, FromB, ToB),
|
|
643
|
+
materialize_rule_environment(RuleA0, RuleA, EnvironmentA),
|
|
644
|
+
materialize_rule_environment(RuleB0, RuleB, EnvironmentB),
|
|
645
|
+
RuleA = rule(_, _, HeadA, BodyA, ExceptionsA, _, _, _, _, _),
|
|
646
|
+
RuleB = rule(_, _, HeadB, BodyB, ExceptionsB, _, _, _, _, _),
|
|
647
|
+
declared_types(VariablesA, EnvironmentA, DeclaredA),
|
|
648
|
+
declared_types(VariablesB, EnvironmentB, DeclaredB),
|
|
649
|
+
append(DeclaredA, DeclaredB, Declared),
|
|
650
|
+
( ExceptionsA == [], ExceptionsB == [],
|
|
651
|
+
HeadA-BodyA =@= HeadB-BodyB
|
|
652
|
+
-> % Variant rules share every instance: identify them and decide
|
|
653
|
+
% whether that one body can hold at all.
|
|
654
|
+
HeadA-BodyA = HeadB-BodyB,
|
|
655
|
+
expression_parts(BodyA, Parts),
|
|
656
|
+
variant_body_status(Parts, Functional, Declared, Status)
|
|
657
|
+
; HeadA = HeadB,
|
|
658
|
+
expression_parts(BodyA, PartsA),
|
|
659
|
+
expression_parts(BodyB, PartsB),
|
|
660
|
+
body_pair_status(PartsA, PartsB, ExceptionsA, ExceptionsB, Functional, Declared, Status)
|
|
661
|
+
).
|
|
662
|
+
|
|
663
|
+
%% variant_body_status(+Parts, +Functional, +Declared, -Status)
|
|
664
|
+
% implements REQ-kibi-truthful-consistency
|
|
665
|
+
% The two rules apply to exactly the same instances, so they collide exactly
|
|
666
|
+
% when the shared body has an instance. contradiction needs that instance to
|
|
667
|
+
% be proven: the functional closure holds, every comparison was translated,
|
|
668
|
+
% nothing is left in Other (negation, disjunction, counts, temporal
|
|
669
|
+
% relations), and the translated constraints are satisfiable under the
|
|
670
|
+
% strict integer reading. disjoint needs the body to be provably empty under
|
|
671
|
+
% the loose integer reading. Anything in between stays unresolved.
|
|
672
|
+
variant_body_status(parts(Atoms, Comparisons, Other), Functional, Declared, Status) :-
|
|
673
|
+
( functional_closure(Atoms, Functional)
|
|
674
|
+
-> translate_comparisons(Comparisons, Constraints, Translation),
|
|
675
|
+
integer_variables(Declared, Loose, Strict),
|
|
676
|
+
( Translation == false
|
|
677
|
+
-> Status = disjoint
|
|
678
|
+
; \+ numeric_constraints_satisfiable(Constraints, Loose)
|
|
679
|
+
-> Status = disjoint
|
|
680
|
+
; Translation == complete,
|
|
681
|
+
Other == [],
|
|
682
|
+
numeric_constraints_satisfiable(Constraints, Strict)
|
|
683
|
+
-> Status = contradiction
|
|
684
|
+
; Status = unresolved
|
|
685
|
+
)
|
|
686
|
+
; Status = disjoint
|
|
687
|
+
).
|
|
688
|
+
|
|
689
|
+
body_pair_status(PartsA, PartsB, ExceptionsA, ExceptionsB, Functional, Declared, Status) :-
|
|
690
|
+
( parts_jointly_unsatisfiable(PartsA, PartsB, Functional, Declared)
|
|
691
|
+
-> Status = disjoint
|
|
692
|
+
; exception_entailed(ExceptionsA, PartsB, Declared)
|
|
693
|
+
-> Status = disjoint
|
|
694
|
+
; exception_entailed(ExceptionsB, PartsA, Declared)
|
|
695
|
+
-> Status = disjoint
|
|
696
|
+
; ExceptionsA == [], ExceptionsB == [],
|
|
697
|
+
( certain_subsumption(PartsA, PartsB, Functional, Declared)
|
|
698
|
+
; certain_subsumption(PartsB, PartsA, Functional, Declared)
|
|
699
|
+
)
|
|
700
|
+
-> Status = contradiction
|
|
701
|
+
; Status = unresolved
|
|
702
|
+
).
|
|
703
|
+
|
|
704
|
+
rule_context_compatible(scope(AuthA, ScopeA, _), FromA, ToA, scope(AuthB, ScopeB, _), FromB, ToB) :-
|
|
705
|
+
(AuthA == '' ; AuthB == '' ; AuthA == AuthB),
|
|
706
|
+
(ScopeA == '' ; ScopeB == '' ; ScopeA == ScopeB),
|
|
707
|
+
(FromA == '' ; ToB == '' ; FromA @=< ToB),
|
|
708
|
+
(FromB == '' ; ToA == '' ; FromB @=< ToA).
|
|
709
|
+
|
|
710
|
+
conflicting_modality(assert, deny).
|
|
711
|
+
conflicting_modality(deny, assert).
|
|
712
|
+
conflicting_modality(oblige, forbid).
|
|
713
|
+
conflicting_modality(forbid, oblige).
|
|
714
|
+
conflicting_modality(permit, forbid).
|
|
715
|
+
conflicting_modality(forbid, permit).
|
|
716
|
+
|
|
717
|
+
%% declared_types(+Variables, +Environment, -Declared)
|
|
718
|
+
% Declared pairs each materialized rule variable with its declared type. The
|
|
719
|
+
% two rules' lists are appended, so after the heads, bodies or functional
|
|
720
|
+
% values are unified a single Prolog variable can carry several declarations.
|
|
721
|
+
declared_types(Variables, Environment, Declared) :-
|
|
722
|
+
convlist(declared_type(Environment), Variables, Declared).
|
|
723
|
+
|
|
724
|
+
declared_type(Environment, variable(Name, Type, _), Variable-Type) :-
|
|
725
|
+
memberchk(Name-Variable, Environment).
|
|
726
|
+
|
|
727
|
+
%% integer_variables(+Declared, -Loose, -Strict)
|
|
728
|
+
% implements REQ-kibi-truthful-consistency
|
|
729
|
+
% Variables declared int or integer range over the integers, so x > 0 and
|
|
730
|
+
% x < 1 cannot both hold for them. A variable shared by both rules may be
|
|
731
|
+
% declared int by one and number by the other; the fragment does not decide
|
|
732
|
+
% which declaration wins, so it reads both ways:
|
|
733
|
+
%
|
|
734
|
+
% Loose variables (compared by identity, after unification) whose every
|
|
735
|
+
% declaration is int or integer. Proofs of emptiness (disjoint)
|
|
736
|
+
% use it: fewer integral variables only make constraints easier to
|
|
737
|
+
% satisfy, so an empty Loose reading is empty under any reading.
|
|
738
|
+
% Strict variables with at least one int or integer declaration. Proofs
|
|
739
|
+
% of an instance (contradiction) use it: an integral witness
|
|
740
|
+
% satisfies every declaration at once.
|
|
741
|
+
%
|
|
742
|
+
% Called after functional closure and atom matching, so identifications made
|
|
743
|
+
% there are seen. convlist/3 and include/3 keep the variables; findall/3
|
|
744
|
+
% would copy them.
|
|
745
|
+
integer_variables(Declared, Loose, Strict) :-
|
|
746
|
+
foldl(collect_declared_variable, Declared, [], Variables),
|
|
747
|
+
include(every_declaration_integer(Declared), Variables, Loose),
|
|
748
|
+
include(some_declaration_integer(Declared), Variables, Strict).
|
|
749
|
+
|
|
750
|
+
collect_declared_variable(Variable-_, Seen, Seen1) :-
|
|
751
|
+
( var(Variable),
|
|
752
|
+
\+ ( member(Known, Seen), Known == Variable )
|
|
753
|
+
-> Seen1 = [Variable|Seen]
|
|
754
|
+
; Seen1 = Seen
|
|
755
|
+
).
|
|
756
|
+
|
|
757
|
+
every_declaration_integer(Declared, Variable) :-
|
|
758
|
+
forall(( member(Other-Type, Declared), Other == Variable ), integer_type(Type)).
|
|
759
|
+
|
|
760
|
+
some_declaration_integer(Declared, Variable) :-
|
|
761
|
+
member(Other-Type, Declared),
|
|
762
|
+
Other == Variable,
|
|
763
|
+
integer_type(Type),
|
|
764
|
+
!.
|
|
765
|
+
|
|
766
|
+
integer_type(int).
|
|
767
|
+
integer_type(integer).
|
|
768
|
+
|
|
769
|
+
%% expression_parts(+Expression, -parts(Atoms, Comparisons, Other))
|
|
770
|
+
% Flatten a conjunctive body. Disjunctions, negations, counts and temporal
|
|
771
|
+
% relations are kept in Other: they can only make an analysis unresolved.
|
|
772
|
+
expression_parts(none, parts([], [], [])) :- !.
|
|
773
|
+
expression_parts(Expression, parts(Atoms, Comparisons, Other)) :-
|
|
774
|
+
flatten_conjunction(Expression, Items),
|
|
775
|
+
partition_items(Items, Atoms, Comparisons, Other).
|
|
776
|
+
|
|
777
|
+
flatten_conjunction(all(Items), Flat) :- !,
|
|
778
|
+
maplist(flatten_conjunction, Items, Nested),
|
|
779
|
+
append(Nested, Flat).
|
|
780
|
+
flatten_conjunction(Item, [Item]).
|
|
781
|
+
|
|
782
|
+
partition_items([], [], [], []).
|
|
783
|
+
partition_items([Item|Rest], [Item|Atoms], Comparisons, Other) :-
|
|
784
|
+
Item = atom(_, _, _, positive, _), !,
|
|
785
|
+
partition_items(Rest, Atoms, Comparisons, Other).
|
|
786
|
+
partition_items([Item|Rest], Atoms, [Item|Comparisons], Other) :-
|
|
787
|
+
Item = compare(_, _, _), !,
|
|
788
|
+
partition_items(Rest, Atoms, Comparisons, Other).
|
|
789
|
+
partition_items([Item|Rest], Atoms, Comparisons, [Item|Other]) :-
|
|
790
|
+
partition_items(Rest, Atoms, Comparisons, Other).
|
|
791
|
+
|
|
792
|
+
%% parts_jointly_unsatisfiable(+PartsA, +PartsB, +Functional, +Declared)
|
|
793
|
+
% True only when the two bodies provably cannot hold for the same instance:
|
|
794
|
+
% a declared functional dependency forces two provably different values, a
|
|
795
|
+
% comparison is false, or the translated comparisons admit no value under the
|
|
796
|
+
% loose integer reading. Comparisons that cannot be translated are left
|
|
797
|
+
% out, which can only make the remaining set easier to satisfy, so they never
|
|
798
|
+
% make the pair disjoint.
|
|
799
|
+
parts_jointly_unsatisfiable(parts(AtomsA, ComparisonsA, _), parts(AtomsB, ComparisonsB, _), Functional, Declared) :-
|
|
800
|
+
\+ \+ (
|
|
801
|
+
append(AtomsA, AtomsB, Atoms),
|
|
802
|
+
( functional_closure(Atoms, Functional)
|
|
803
|
+
-> append(ComparisonsA, ComparisonsB, Comparisons),
|
|
804
|
+
translate_comparisons(Comparisons, Constraints, Status),
|
|
805
|
+
( Status == false
|
|
806
|
+
-> true
|
|
807
|
+
; integer_variables(Declared, Loose, _Strict),
|
|
808
|
+
\+ numeric_constraints_satisfiable(Constraints, Loose)
|
|
809
|
+
)
|
|
810
|
+
; true
|
|
811
|
+
)
|
|
812
|
+
).
|
|
813
|
+
|
|
814
|
+
%% functional_closure(+Atoms, +Functional)
|
|
815
|
+
% Unify the value arguments of every pair of atoms whose predicate declares
|
|
816
|
+
% key arguments and whose key arguments are identical, until nothing changes.
|
|
817
|
+
% Fails when two such atoms carry provably different ground values. Atoms of
|
|
818
|
+
% undeclared predicates are never identified.
|
|
819
|
+
functional_closure(Atoms, Functional) :-
|
|
820
|
+
atom_pairs(Atoms, Pairs),
|
|
821
|
+
foldl(identify_functional_pair(Functional), Pairs, false, Changed),
|
|
822
|
+
( Changed == true
|
|
823
|
+
-> functional_closure(Atoms, Functional)
|
|
824
|
+
; true
|
|
825
|
+
).
|
|
826
|
+
|
|
827
|
+
atom_pairs([], []).
|
|
828
|
+
atom_pairs([Atom|Rest], Pairs) :-
|
|
829
|
+
maplist(atom_pair(Atom), Rest, Head),
|
|
830
|
+
atom_pairs(Rest, Tail),
|
|
831
|
+
append(Head, Tail, Pairs).
|
|
832
|
+
|
|
833
|
+
atom_pair(Left, Right, Left-Right).
|
|
834
|
+
|
|
835
|
+
identify_functional_pair(Functional, Left-Right, Changed0, Changed) :-
|
|
836
|
+
Left = atom(Namespace, Name, ArgsLeft, _, _),
|
|
837
|
+
Right = atom(NamespaceRight, NameRight, ArgsRight, _, _),
|
|
838
|
+
( Namespace == NamespaceRight,
|
|
839
|
+
Name == NameRight,
|
|
840
|
+
length(ArgsLeft, Arity),
|
|
841
|
+
length(ArgsRight, Arity)
|
|
842
|
+
-> findall(Keys, member(functional(Namespace, Name, Arity, Keys), Functional), KeySets),
|
|
843
|
+
foldl(identify_by_keys(ArgsLeft, ArgsRight), KeySets, Changed0, Changed)
|
|
844
|
+
; Changed = Changed0
|
|
845
|
+
).
|
|
846
|
+
|
|
847
|
+
identify_by_keys(ArgsLeft, ArgsRight, Keys, Changed0, Changed) :-
|
|
848
|
+
( forall(member(Position, Keys),
|
|
849
|
+
( nth1(Position, ArgsLeft, KeyLeft),
|
|
850
|
+
nth1(Position, ArgsRight, KeyRight),
|
|
851
|
+
KeyLeft == KeyRight ))
|
|
852
|
+
-> identify_values(ArgsLeft, ArgsRight, Keys, 1, Changed0, Changed)
|
|
853
|
+
; Changed = Changed0
|
|
854
|
+
).
|
|
855
|
+
|
|
856
|
+
identify_values([], [], _, _, Changed, Changed).
|
|
857
|
+
identify_values([Left|LeftRest], [Right|RightRest], Keys, Position, Changed0, Changed) :-
|
|
858
|
+
( memberchk(Position, Keys)
|
|
859
|
+
-> Changed1 = Changed0
|
|
860
|
+
; identify_value(Left, Right, Changed0, Changed1)
|
|
861
|
+
),
|
|
862
|
+
Next is Position + 1,
|
|
863
|
+
identify_values(LeftRest, RightRest, Keys, Next, Changed1, Changed).
|
|
864
|
+
|
|
865
|
+
identify_value(Left, Right, Changed0, Changed) :-
|
|
866
|
+
( Left == Right
|
|
867
|
+
-> Changed = Changed0
|
|
868
|
+
; ( var(Left) ; var(Right) )
|
|
869
|
+
-> Left = Right,
|
|
870
|
+
Changed = true
|
|
871
|
+
; values_distinct(Left, Right)
|
|
872
|
+
-> fail
|
|
873
|
+
; % Equal values in different spellings (1000 ms and 1 s), or values
|
|
874
|
+
% this fragment cannot compare: identify nothing.
|
|
875
|
+
Changed = Changed0
|
|
876
|
+
).
|
|
877
|
+
|
|
878
|
+
values_distinct(Left, Right) :-
|
|
879
|
+
ir_ground(Left),
|
|
880
|
+
ir_ground(Right),
|
|
881
|
+
catch(term_value(Left, LeftValue), _, fail),
|
|
882
|
+
catch(term_value(Right, RightValue), _, fail),
|
|
883
|
+
( number(LeftValue), number(RightValue)
|
|
884
|
+
-> LeftValue =\= RightValue
|
|
885
|
+
; LeftValue \== RightValue
|
|
886
|
+
).
|
|
887
|
+
|
|
888
|
+
%% certain_subsumption(+General, +Specific, +Functional, +Declared)
|
|
889
|
+
% Every instance of Specific's body is an instance of General's body, and
|
|
890
|
+
% Specific's body provably has an instance: both bodies are plain
|
|
891
|
+
% conjunctions, every Specific comparison was translated (none is unknown),
|
|
892
|
+
% and the translated comparisons are satisfiable under the strict integer
|
|
893
|
+
% reading. An untranslatable comparison such as X < Y can make Specific's
|
|
894
|
+
% body empty, so it blocks a contradiction verdict. The integer readings are
|
|
895
|
+
% taken after atom matching, which can identify a General variable with a
|
|
896
|
+
% Specific one.
|
|
897
|
+
certain_subsumption(General, Specific, Functional, Declared) :-
|
|
898
|
+
General = parts(GeneralAtoms, GeneralComparisons, []),
|
|
899
|
+
Specific = parts(SpecificAtoms, SpecificComparisons, []),
|
|
900
|
+
\+ \+ (
|
|
901
|
+
functional_closure(SpecificAtoms, Functional),
|
|
902
|
+
translate_comparisons(SpecificComparisons, Premises, complete),
|
|
903
|
+
match_atoms(GeneralAtoms, SpecificAtoms),
|
|
904
|
+
integer_variables(Declared, _Loose, Strict),
|
|
905
|
+
numeric_constraints_satisfiable(Premises, Strict),
|
|
906
|
+
forall(member(Comparison, GeneralComparisons),
|
|
907
|
+
comparison_entailed(Premises, Strict, Comparison))
|
|
908
|
+
).
|
|
909
|
+
|
|
910
|
+
%% parts_subsume(+General, +Specific, +Declared)
|
|
911
|
+
% Every instance of Specific's body is an instance of General's body: each
|
|
912
|
+
% General atom matches a Specific atom without binding Specific's variables,
|
|
913
|
+
% and each General comparison is entailed by Specific's translated
|
|
914
|
+
% comparisons under the loose integer reading. Untranslated premises are
|
|
915
|
+
% dropped, which only weakens what is entailed.
|
|
916
|
+
parts_subsume(parts(GeneralAtoms, GeneralComparisons, []), parts(SpecificAtoms, SpecificComparisons, _), Declared) :-
|
|
917
|
+
match_atoms(GeneralAtoms, SpecificAtoms),
|
|
918
|
+
translate_comparisons(SpecificComparisons, Premises, _PremiseStatus),
|
|
919
|
+
integer_variables(Declared, Loose, _Strict),
|
|
920
|
+
forall(member(Comparison, GeneralComparisons), comparison_entailed(Premises, Loose, Comparison)).
|
|
921
|
+
|
|
922
|
+
match_atoms([], _).
|
|
923
|
+
match_atoms([Atom|Rest], Specific) :-
|
|
924
|
+
member(Candidate, Specific),
|
|
925
|
+
subsumes_term(Atom, Candidate),
|
|
926
|
+
Atom = Candidate,
|
|
927
|
+
match_atoms(Rest, Specific).
|
|
928
|
+
|
|
929
|
+
comparison_entailed(Premises, Integers, compare(Op, Left, Right)) :-
|
|
930
|
+
translate_comparison(compare(Op, Left, Right), Result),
|
|
931
|
+
( Result == true
|
|
932
|
+
-> true
|
|
933
|
+
; Result = constraint(Constraint)
|
|
934
|
+
-> numeric_constraint_entailed(Premises, Constraint, Integers)
|
|
935
|
+
; fail
|
|
936
|
+
).
|
|
937
|
+
|
|
938
|
+
%% exception_entailed(+Exceptions, +OtherParts, +Declared)
|
|
939
|
+
% An exception that holds whenever the other rule's body holds removes every
|
|
940
|
+
% shared instance, so the rules cannot collide.
|
|
941
|
+
exception_entailed(Exceptions, OtherParts, Declared) :-
|
|
942
|
+
member(Exception, Exceptions),
|
|
943
|
+
expression_parts(Exception, ExceptionParts),
|
|
944
|
+
ExceptionParts = parts(_, _, []),
|
|
945
|
+
\+ \+ parts_subsume(ExceptionParts, OtherParts, Declared), !.
|
|
946
|
+
|
|
947
|
+
%% translate_comparisons(+Comparisons, -Constraints, -Status)
|
|
948
|
+
% Status is false when a comparison is certainly false, unknown when some
|
|
949
|
+
% comparison could not be translated, and complete otherwise.
|
|
950
|
+
translate_comparisons(Comparisons, Constraints, Status) :-
|
|
951
|
+
foldl(translate_into, Comparisons, acc([], complete), acc(Reversed, Status)),
|
|
952
|
+
reverse(Reversed, Constraints).
|
|
953
|
+
|
|
954
|
+
translate_into(_, acc(Constraints, false), acc(Constraints, false)) :- !.
|
|
955
|
+
translate_into(Comparison, acc(Constraints, Status), acc(Constraints1, Status1)) :-
|
|
956
|
+
translate_comparison(Comparison, Result),
|
|
957
|
+
( Result == true -> Constraints1 = Constraints, Status1 = Status
|
|
958
|
+
; Result == false -> Constraints1 = Constraints, Status1 = false
|
|
959
|
+
; Result = constraint(Constraint) -> Constraints1 = [Constraint|Constraints], Status1 = Status
|
|
960
|
+
; Constraints1 = Constraints, Status1 = unknown
|
|
961
|
+
).
|
|
962
|
+
|
|
963
|
+
translate_comparison(compare(Op, Left, Right), Result) :-
|
|
964
|
+
comparison_operand(Left, L),
|
|
965
|
+
comparison_operand(Right, R),
|
|
966
|
+
translate_operands(Op, L, R, Result).
|
|
967
|
+
|
|
968
|
+
translate_operands(Op, num(L), num(R), Result) :- !,
|
|
969
|
+
( numeric_constraint_holds(Op, L, R) -> Result = true ; Result = false ).
|
|
970
|
+
translate_operands(Op, var(V), num(N), constraint(c(Op, V, N))) :- !.
|
|
971
|
+
translate_operands(Op, num(N), var(V), constraint(c(Flipped, V, N))) :- !,
|
|
972
|
+
flipped_operator(Op, Flipped).
|
|
973
|
+
% The same variable on both sides is decided exactly: X < X never holds and
|
|
974
|
+
% X =< X always does, whatever X denotes.
|
|
975
|
+
translate_operands(Op, var(L), var(R), Result) :-
|
|
976
|
+
L == R, !,
|
|
977
|
+
( reflexive_operator(Op) -> Result = true ; Result = false ).
|
|
978
|
+
translate_operands(Op, ground(L), ground(R), Result) :- !,
|
|
979
|
+
( catch(compare_terms(Op, L, R), _, fail) -> Result = true ; Result = false ).
|
|
980
|
+
translate_operands(_, _, _, unknown).
|
|
981
|
+
|
|
982
|
+
reflexive_operator(eq).
|
|
983
|
+
reflexive_operator(lte).
|
|
984
|
+
reflexive_operator(gte).
|
|
985
|
+
|
|
986
|
+
comparison_operand(Term, var(Term)) :- var(Term), !.
|
|
987
|
+
comparison_operand(Term, num(Value)) :-
|
|
988
|
+
ir_ground(Term),
|
|
989
|
+
catch(term_value(Term, Value), _, fail),
|
|
990
|
+
number(Value), !.
|
|
991
|
+
comparison_operand(Term, ground(Term)) :- ir_ground(Term), !.
|
|
992
|
+
comparison_operand(Term, other(Term)).
|
|
993
|
+
|
|
994
|
+
% IR terms keep variables as var(Name, Type) data until materialized.
|
|
995
|
+
ir_ground(Term) :- ground(Term), \+ sub_term(var(_, _), Term).
|
|
996
|
+
|
|
997
|
+
flipped_operator(eq, eq).
|
|
998
|
+
flipped_operator(neq, neq).
|
|
999
|
+
flipped_operator(lt, gt).
|
|
1000
|
+
flipped_operator(gt, lt).
|
|
1001
|
+
flipped_operator(lte, gte).
|
|
1002
|
+
flipped_operator(gte, lte).
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
%% logic_rule_fold_exceptions(+Rule, -Folded)
|
|
1006
|
+
% implements REQ-kibi-truthful-consistency
|
|
1007
|
+
% "forbid H :- B unless E1, ..., En" fires exactly when B holds and no Ei
|
|
1008
|
+
% does. When every exception is a single comparison, its negation is again
|
|
1009
|
+
% a comparison (lt and gte, eq and neq, ...), so the rule is equivalent to
|
|
1010
|
+
% "forbid H :- B, not E1, ..., not En" without exceptions. This is how an
|
|
1011
|
+
% "only when C" requirement (forbid unless C) is compared: the conflict and
|
|
1012
|
+
% scenario checks then reason about one body. Rules with any other kind of
|
|
1013
|
+
% exception are returned unchanged.
|
|
1014
|
+
logic_rule_fold_exceptions(
|
|
1015
|
+
rule(Kind, Modality, Head, Body, Exceptions, Scope, From, To, Schema, Variables),
|
|
1016
|
+
rule(Kind, Modality, Head, FoldedBody, Remaining, Scope, From, To, Schema, Variables)
|
|
1017
|
+
) :-
|
|
1018
|
+
( Exceptions \== [],
|
|
1019
|
+
maplist(negated_comparison, Exceptions, Negated)
|
|
1020
|
+
-> fold_body(Body, Negated, FoldedBody),
|
|
1021
|
+
Remaining = []
|
|
1022
|
+
; FoldedBody = Body,
|
|
1023
|
+
Remaining = Exceptions
|
|
1024
|
+
).
|
|
1025
|
+
|
|
1026
|
+
negated_comparison(compare(Op, Left, Right), compare(Negated, Left, Right)) :-
|
|
1027
|
+
comparison_negation(Op, Negated).
|
|
1028
|
+
|
|
1029
|
+
comparison_negation(eq, neq).
|
|
1030
|
+
comparison_negation(neq, eq).
|
|
1031
|
+
comparison_negation(lt, gte).
|
|
1032
|
+
comparison_negation(gte, lt).
|
|
1033
|
+
comparison_negation(gt, lte).
|
|
1034
|
+
comparison_negation(lte, gt).
|
|
1035
|
+
|
|
1036
|
+
fold_body(none, [Single], Single) :- !.
|
|
1037
|
+
fold_body(none, Items, all(Items)) :- !.
|
|
1038
|
+
fold_body(all(Items0), Extra, all(Items)) :- !, append(Items0, Extra, Items).
|
|
1039
|
+
fold_body(Body, Extra, all([Body|Extra])).
|
|
1040
|
+
|
|
1041
|
+
%% logic_rule_property_form(+Rule, -Form)
|
|
1042
|
+
% implements REQ-kibi-scenario-feasibility-v2
|
|
1043
|
+
% Read a rule as a restriction over subject properties: the form scenario
|
|
1044
|
+
% feasibility shares with the property lane. A body atom reads a property
|
|
1045
|
+
% when its namespace is the property's subject_key and its name the
|
|
1046
|
+
% property_key; its last argument is the property's value and any earlier
|
|
1047
|
+
% arguments name the instance. After folding comparison exceptions
|
|
1048
|
+
% (logic_rule_fold_exceptions/2) Form is
|
|
1049
|
+
%
|
|
1050
|
+
% property_form(Modality, Head, ScopeName, From, To, Keys, Conditions)
|
|
1051
|
+
%
|
|
1052
|
+
% Keys are the Subject-Property pairs of every property atom in the body and
|
|
1053
|
+
% remaining exceptions, however nested. Conditions is
|
|
1054
|
+
% * a list of cond(Subject-Property, Op, Term, ValueType): the rule fires
|
|
1055
|
+
% exactly when every condition holds (an empty list fires whenever the
|
|
1056
|
+
% read properties have values);
|
|
1057
|
+
% * never: a comparison between two constants is false, so the rule never
|
|
1058
|
+
% fires;
|
|
1059
|
+
% * untranslatable: the body uses something this reading cannot decide (a
|
|
1060
|
+
% negation, disjunction, count or temporal relation, an atom that is not
|
|
1061
|
+
% a property read, two reads of one property with different values, a
|
|
1062
|
+
% property value reused as an instance, a comparison between two
|
|
1063
|
+
% properties, or an exception that is not a single comparison).
|
|
1064
|
+
logic_rule_property_form(Rule0, property_form(Modality, Head, ScopeName, From, To, Keys, Conditions)) :-
|
|
1065
|
+
logic_rule_fold_exceptions(Rule0, Rule),
|
|
1066
|
+
Rule = rule(_Kind, Modality, Head, Body, Exceptions, scope(_, ScopeName, _), From, To, _Schema, _Variables),
|
|
1067
|
+
findall(Key,
|
|
1068
|
+
( member(Expression, [Body|Exceptions]),
|
|
1069
|
+
expression_property_key(Expression, Key)
|
|
1070
|
+
),
|
|
1071
|
+
Keys0),
|
|
1072
|
+
sort(Keys0, Keys),
|
|
1073
|
+
expression_parts(Body, parts(Atoms, Comparisons, Other)),
|
|
1074
|
+
( Exceptions == [],
|
|
1075
|
+
Other == [],
|
|
1076
|
+
maplist(property_read, Atoms, Reads),
|
|
1077
|
+
property_value_bindings(Reads, Head, Bindings, ConstantConditions)
|
|
1078
|
+
-> foldl(comparison_condition(Bindings), Comparisons, ConstantConditions, Conditions0),
|
|
1079
|
+
( Conditions0 == untranslatable
|
|
1080
|
+
-> Conditions = untranslatable
|
|
1081
|
+
; Conditions0 == never
|
|
1082
|
+
-> Conditions = never
|
|
1083
|
+
; reverse(Conditions0, Conditions)
|
|
1084
|
+
)
|
|
1085
|
+
; Conditions = untranslatable
|
|
1086
|
+
).
|
|
1087
|
+
|
|
1088
|
+
expression_property_key(atom(Namespace, Name, Args, _, _), Namespace-Name) :-
|
|
1089
|
+
Namespace \== default,
|
|
1090
|
+
Args \== [].
|
|
1091
|
+
expression_property_key(all(Items), Key) :-
|
|
1092
|
+
member(Item, Items),
|
|
1093
|
+
expression_property_key(Item, Key).
|
|
1094
|
+
expression_property_key(any(Items), Key) :-
|
|
1095
|
+
member(Item, Items),
|
|
1096
|
+
expression_property_key(Item, Key).
|
|
1097
|
+
expression_property_key(not(Atom), Key) :-
|
|
1098
|
+
expression_property_key(Atom, Key).
|
|
1099
|
+
expression_property_key(count(Atom, _, _), Key) :-
|
|
1100
|
+
expression_property_key(Atom, Key).
|
|
1101
|
+
|
|
1102
|
+
property_read(atom(Namespace, Name, Args, positive, _), read(Namespace-Name, Value, Instance)) :-
|
|
1103
|
+
Namespace \== default,
|
|
1104
|
+
Args \== [],
|
|
1105
|
+
append(Instance, [Value], Args).
|
|
1106
|
+
|
|
1107
|
+
% Bindings maps each value variable name to its property and declared type.
|
|
1108
|
+
% A read with a constant value is a condition on that property.
|
|
1109
|
+
property_value_bindings(Reads, Head, Bindings, ConstantConditions) :-
|
|
1110
|
+
findall(Name, (member(read(_, _, Instance), Reads), member(var(Name, _), Instance)), InstanceNames),
|
|
1111
|
+
( Head = atom(_, _, HeadArgs, _, _)
|
|
1112
|
+
-> findall(Name, member(var(Name, _), HeadArgs), HeadNames)
|
|
1113
|
+
; HeadNames = []
|
|
1114
|
+
),
|
|
1115
|
+
append(InstanceNames, HeadNames, IdentityNames),
|
|
1116
|
+
foldl(read_binding(IdentityNames), Reads, acc([], []), acc(Bindings, ConstantConditions)),
|
|
1117
|
+
% One property, one value: two reads of the same property must agree.
|
|
1118
|
+
forall(
|
|
1119
|
+
( member(read(Key, ValueA, _), Reads),
|
|
1120
|
+
member(read(Key, ValueB, _), Reads)
|
|
1121
|
+
),
|
|
1122
|
+
ValueA == ValueB
|
|
1123
|
+
).
|
|
1124
|
+
|
|
1125
|
+
read_binding(IdentityNames, read(Key, var(Name, Type), _), acc(Bindings0, Conditions), acc(Bindings, Conditions)) :-
|
|
1126
|
+
!,
|
|
1127
|
+
\+ memberchk(Name, IdentityNames),
|
|
1128
|
+
( memberchk(Name-OtherKey-_, Bindings0)
|
|
1129
|
+
-> OtherKey == Key,
|
|
1130
|
+
Bindings = Bindings0
|
|
1131
|
+
; Bindings = [Name-Key-Type|Bindings0]
|
|
1132
|
+
).
|
|
1133
|
+
read_binding(_, read(Key, Value, _), acc(Bindings, Conditions), acc(Bindings, [cond(Key, eq, Value, value)|Conditions])) :-
|
|
1134
|
+
constant_term(Value).
|
|
1135
|
+
|
|
1136
|
+
constant_term(number(_, _)).
|
|
1137
|
+
constant_term(duration(_, _)).
|
|
1138
|
+
constant_term(const(_, _)).
|
|
1139
|
+
|
|
1140
|
+
comparison_condition(_, _, untranslatable, untranslatable) :- !.
|
|
1141
|
+
comparison_condition(_, _, never, never) :- !.
|
|
1142
|
+
comparison_condition(Bindings, compare(Op, Left, Right), Conditions, Next) :-
|
|
1143
|
+
condition_operand(Bindings, Left, LeftOperand),
|
|
1144
|
+
condition_operand(Bindings, Right, RightOperand),
|
|
1145
|
+
( LeftOperand = property(Key, Type), RightOperand = constant(Term)
|
|
1146
|
+
-> Next = [cond(Key, Op, Term, Type)|Conditions]
|
|
1147
|
+
; LeftOperand = constant(Term), RightOperand = property(Key, Type)
|
|
1148
|
+
-> flipped_operator(Op, Flipped),
|
|
1149
|
+
Next = [cond(Key, Flipped, Term, Type)|Conditions]
|
|
1150
|
+
; LeftOperand = constant(_), RightOperand = constant(_)
|
|
1151
|
+
-> ( catch(compare_terms(Op, Left, Right), _, fail)
|
|
1152
|
+
-> Next = Conditions
|
|
1153
|
+
; catch(compare_terms(Op, Left, Right), _, true)
|
|
1154
|
+
-> Next = untranslatable
|
|
1155
|
+
; Next = never
|
|
1156
|
+
)
|
|
1157
|
+
; Next = untranslatable
|
|
1158
|
+
).
|
|
1159
|
+
|
|
1160
|
+
condition_operand(Bindings, var(Name, _), property(Key, Type)) :-
|
|
1161
|
+
memberchk(Name-Key-Type, Bindings), !.
|
|
1162
|
+
condition_operand(_, Term, constant(Term)) :-
|
|
1163
|
+
constant_term(Term), !.
|
|
1164
|
+
condition_operand(_, Term, other(Term)).
|
|
501
1165
|
|
|
502
1166
|
%% logic_rules_stratified(+Rules)
|
|
503
1167
|
% Check the finite dependency graph for a negated edge on a cycle. A single
|
|
@@ -574,56 +1238,6 @@ logic_rule_conflict_witness(RuleA, RuleB, Witness) :-
|
|
|
574
1238
|
rule_schema_b: SchemaB
|
|
575
1239
|
}.
|
|
576
1240
|
|
|
577
|
-
opposing_rule_heads(rule(_, ModalityA, HeadA, _, _, _, _, _, _, _), rule(_, ModalityB, HeadB, _, _, _, _, _, _, _)) :-
|
|
578
|
-
conflicting_modality(ModalityA, ModalityB),
|
|
579
|
-
copy_term((HeadA, HeadB), (CopyA, CopyB)),
|
|
580
|
-
CopyA = CopyB.
|
|
581
|
-
|
|
582
|
-
conflicting_modality(assert, deny).
|
|
583
|
-
conflicting_modality(deny, assert).
|
|
584
|
-
conflicting_modality(oblige, forbid).
|
|
585
|
-
conflicting_modality(forbid, oblige).
|
|
586
|
-
conflicting_modality(permit, forbid).
|
|
587
|
-
conflicting_modality(forbid, permit).
|
|
588
|
-
|
|
589
|
-
compatible_rule_context(rule(_, _, _, BodyA, _, scope(AuthA, ScopeA, _), FromA, ToA, _, _), rule(_, _, _, BodyB, _, scope(AuthB, ScopeB, _), FromB, ToB, _, _)) :-
|
|
590
|
-
(AuthA == '' ; AuthB == '' ; AuthA == AuthB),
|
|
591
|
-
(ScopeA == '' ; ScopeB == '' ; ScopeA == ScopeB),
|
|
592
|
-
(FromA == '' ; ToB == '' ; FromA @=< ToB),
|
|
593
|
-
(FromB == '' ; ToA == '' ; FromB @=< ToA),
|
|
594
|
-
bodies_may_overlap(BodyA, BodyB).
|
|
595
|
-
|
|
596
|
-
bodies_may_overlap(none, _).
|
|
597
|
-
bodies_may_overlap(_, none).
|
|
598
|
-
bodies_may_overlap(BodyA, BodyB) :-
|
|
599
|
-
BodyA \= none,
|
|
600
|
-
BodyB \= none,
|
|
601
|
-
\+ bodies_definitely_disjoint(BodyA, BodyB).
|
|
602
|
-
|
|
603
|
-
bodies_definitely_disjoint(BodyA, _BodyB) :- expression_definitely_false(BodyA), !.
|
|
604
|
-
bodies_definitely_disjoint(_BodyA, BodyB) :- expression_definitely_false(BodyB), !.
|
|
605
|
-
|
|
606
|
-
% Only classify a body as impossible when its finite, ground comparison or
|
|
607
|
-
% temporal constraint can be evaluated to false. Unknown atoms, variables,
|
|
608
|
-
% and unsupported combinations remain unresolved rather than being treated as
|
|
609
|
-
% disjoint by guesswork.
|
|
610
|
-
expression_definitely_false(compare(Op, Left, Right)) :-
|
|
611
|
-
ground(compare(Op, Left, Right)),
|
|
612
|
-
\+ catch(compare_terms(Op, Left, Right), _, fail).
|
|
613
|
-
expression_definitely_false(temporal(Relation, Left, Right)) :-
|
|
614
|
-
ground(temporal(Relation, Left, Right)),
|
|
615
|
-
\+ catch(temporal_holds(Relation, Left, Right), _, fail).
|
|
616
|
-
expression_definitely_false(all(Items)) :-
|
|
617
|
-
member(Item, Items),
|
|
618
|
-
expression_definitely_false(Item), !.
|
|
619
|
-
expression_definitely_false(any(Items)) :-
|
|
620
|
-
Items \= [],
|
|
621
|
-
maplist(expression_definitely_false, Items).
|
|
622
|
-
|
|
623
|
-
same_rule_body(RuleA, RuleB) :-
|
|
624
|
-
RuleA = rule(_, _, _, BodyA, ExceptionsA, ScopeA, FromA, ToA, _, _),
|
|
625
|
-
RuleB = rule(_, _, _, BodyB, ExceptionsB, ScopeB, FromB, ToB, _, _),
|
|
626
|
-
BodyA == BodyB, ExceptionsA == ExceptionsB, ScopeA == ScopeB, FromA == FromB, ToA == ToB.
|
|
627
1241
|
|
|
628
1242
|
normalize_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
629
1243
|
normalize_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|