kibi-core 0.22.5 → 0.24.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 +1 -1
- package/schema/entities.pl +6 -0
- package/schema/validation.pl +4 -1
- package/src/certificate_check.pl +783 -0
- package/src/certificates.pl +245 -0
- package/src/checks.pl +23 -5
- package/src/claim_obligations.pl +378 -0
- package/src/code_certificates.pl +297 -0
- package/src/discovery.pl +176 -9
- package/src/intervals.pl +11 -0
- package/src/kb.pl +7 -0
- package/src/kb_consistency.pl +213 -0
- package/src/requirement_proof.pl +628 -33
- package/src/rule_registry.pl +20 -0
- package/src/semantic_quality.pl +1 -1
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
% Module: certificate_check
|
|
2
|
+
% implements REQ-kibi-kb-certificates
|
|
3
|
+
%
|
|
4
|
+
% The independent checker behind `kibi verify`. It states each KB theorem
|
|
5
|
+
% over the facts current requirements name and re-checks a certificate for
|
|
6
|
+
% it. It never searches: a model is checked by evaluating every constraint
|
|
7
|
+
% against the model's value, a contradiction core by re-running the interval
|
|
8
|
+
% decision procedure on the core alone (plus one model per dropped fact for
|
|
9
|
+
% minimality), and a stratification by checking every dependency edge
|
|
10
|
+
% against the stratum numbers. It does not load checks.pl or the emitter
|
|
11
|
+
% (certificates.pl), so a bug in the search that finds certificates cannot
|
|
12
|
+
% make a wrong certificate pass.
|
|
13
|
+
%
|
|
14
|
+
% Theorems and their certificates (kibi.certificates.v1):
|
|
15
|
+
%
|
|
16
|
+
% properties_consistent One property_model per (subject, property, scope
|
|
17
|
+
% context): a value that satisfies every fact in the
|
|
18
|
+
% context. Otherwise a property_contradiction (an
|
|
19
|
+
% irreducible core with one model per dropped fact),
|
|
20
|
+
% or property_unresolved when the core's validity
|
|
21
|
+
% windows never overlap.
|
|
22
|
+
% predicates_consistent One predicate_model per ground atom (true or
|
|
23
|
+
% false), or a predicate_contradiction pair.
|
|
24
|
+
% decisions_consistent One decision_model per decision key (allow or
|
|
25
|
+
% deny), or a decision_contradiction pair.
|
|
26
|
+
% rules_stratified A rule_strata assignment, or a rule_negation_cycle.
|
|
27
|
+
%
|
|
28
|
+
% A context is a set of facts on one subject and property that can be
|
|
29
|
+
% compared: an unscoped fact belongs to every scope context, a scoped fact
|
|
30
|
+
% only to its own. A context the checker cannot compare (mixed value types,
|
|
31
|
+
% mixed units, an unsupported operator or polarity) is unresolved and needs
|
|
32
|
+
% no certificate; it never counts as consistent.
|
|
33
|
+
|
|
34
|
+
:- module(certificate_check, [
|
|
35
|
+
certificate_property_contexts/1,
|
|
36
|
+
certificate_predicate_groups/1,
|
|
37
|
+
certificate_decision_groups/1,
|
|
38
|
+
certificate_rule_edges/1,
|
|
39
|
+
certificate_member_holds/3,
|
|
40
|
+
certificate_members_satisfiable/2,
|
|
41
|
+
set_texts/2,
|
|
42
|
+
verify_certificates/2,
|
|
43
|
+
kb_verify_certificates_file/2
|
|
44
|
+
]).
|
|
45
|
+
|
|
46
|
+
:- encoding(utf8).
|
|
47
|
+
|
|
48
|
+
:- use_module(library(http/json)).
|
|
49
|
+
:- use_module(library(apply)).
|
|
50
|
+
:- use_module(library(lists)).
|
|
51
|
+
:- use_module(library(pairs)).
|
|
52
|
+
:- use_module('kb.pl', [kb_entity/3, normalize_term_atom/2]).
|
|
53
|
+
:- use_module('intervals.pl', [
|
|
54
|
+
numeric_constraints_satisfiable/2,
|
|
55
|
+
numeric_constraint_holds/3,
|
|
56
|
+
numeric_constraint_negation/2
|
|
57
|
+
]).
|
|
58
|
+
:- use_module('logic_ir.pl', [logic_rule_from_props/2]).
|
|
59
|
+
|
|
60
|
+
certificates_version('kibi.certificates.v1').
|
|
61
|
+
verification_version('kibi.verification.v1').
|
|
62
|
+
|
|
63
|
+
%% kb_verify_certificates_file(+Path, -JsonString)
|
|
64
|
+
% Read a certificate bundle from Path, verify it against the current KB and
|
|
65
|
+
% return the verification report as a JSON string.
|
|
66
|
+
kb_verify_certificates_file(Path, JsonString) :-
|
|
67
|
+
setup_call_cleanup(
|
|
68
|
+
open(Path, read, Stream, [encoding(utf8)]),
|
|
69
|
+
json_read_dict(Stream, Bundle, [value_string_as(string)]),
|
|
70
|
+
close(Stream)),
|
|
71
|
+
verify_certificates(Bundle, Report),
|
|
72
|
+
with_output_to(string(JsonString), json_write_dict(current_output, Report, [width(0)])).
|
|
73
|
+
|
|
74
|
+
%% verify_certificates(+Bundle, -Report)
|
|
75
|
+
% Bundle is a kibi.certificates.v1 dict as read from JSON. Report has
|
|
76
|
+
% status (verified or rejected), one entry per theorem with its verdict
|
|
77
|
+
% (proved, disproved or unresolved), and the rejected certificates.
|
|
78
|
+
verify_certificates(Bundle, Report) :-
|
|
79
|
+
verification_version(Version),
|
|
80
|
+
( bundle_certificates(Bundle, Certificates)
|
|
81
|
+
-> numbered(Certificates, Numbered),
|
|
82
|
+
certificate_property_contexts(Contexts),
|
|
83
|
+
certificate_predicate_groups(AtomGroups),
|
|
84
|
+
certificate_decision_groups(DecisionGroups),
|
|
85
|
+
certificate_rule_edges(Edges),
|
|
86
|
+
property_theorem(Contexts, Numbered, PropertyTheorem, _, PropertyRejected),
|
|
87
|
+
group_theorem(predicates_consistent, predicate, AtomGroups, Numbered, PredicateTheorem, _, PredicateRejected),
|
|
88
|
+
group_theorem(decisions_consistent, decision, DecisionGroups, Numbered, DecisionTheorem, _, DecisionRejected),
|
|
89
|
+
rules_theorem(Edges, Numbered, RulesTheorem, _, RulesRejected),
|
|
90
|
+
findall(_{certificate: Index, kind: Kind, reason: "unknown certificate kind"},
|
|
91
|
+
( member(Index-Certificate, Numbered),
|
|
92
|
+
certificate_kind(Certificate, Kind),
|
|
93
|
+
\+ known_kind(Kind)
|
|
94
|
+
),
|
|
95
|
+
Unmatched),
|
|
96
|
+
append([PropertyRejected, PredicateRejected, DecisionRejected, RulesRejected, Unmatched], Rejected0),
|
|
97
|
+
sort(certificate, @=<, Rejected0, Rejected),
|
|
98
|
+
Theorems = [PropertyTheorem, PredicateTheorem, DecisionTheorem, RulesTheorem]
|
|
99
|
+
; Theorems = [],
|
|
100
|
+
Rejected = [_{certificate: 0, kind: bundle, reason: "not a kibi.certificates.v1 bundle"}],
|
|
101
|
+
Numbered = []
|
|
102
|
+
),
|
|
103
|
+
length(Numbered, Count),
|
|
104
|
+
findall(Index, (member(Failure, Rejected), Index = Failure.certificate, Index > 0), FailedIndexes0),
|
|
105
|
+
sort(FailedIndexes0, FailedIndexes),
|
|
106
|
+
length(FailedIndexes, FailedCount),
|
|
107
|
+
Verified is Count - FailedCount,
|
|
108
|
+
( Rejected == [] -> Status = verified ; Status = rejected ),
|
|
109
|
+
Report = _{
|
|
110
|
+
version: Version,
|
|
111
|
+
status: Status,
|
|
112
|
+
certificates: Count,
|
|
113
|
+
verified: Verified,
|
|
114
|
+
rejected: Rejected,
|
|
115
|
+
theorems: Theorems,
|
|
116
|
+
notCovered: [rule_conflicts, scenario_feasibility]
|
|
117
|
+
}.
|
|
118
|
+
|
|
119
|
+
bundle_certificates(Bundle, Certificates) :-
|
|
120
|
+
is_dict(Bundle),
|
|
121
|
+
get_dict(version, Bundle, Version),
|
|
122
|
+
certificates_version(Expected),
|
|
123
|
+
text_equal(Version, Expected),
|
|
124
|
+
get_dict(certificates, Bundle, Certificates),
|
|
125
|
+
is_list(Certificates),
|
|
126
|
+
forall(member(Certificate, Certificates), is_dict(Certificate)).
|
|
127
|
+
|
|
128
|
+
numbered(Items, Numbered) :-
|
|
129
|
+
findall(Index-Item, nth1(Index, Items, Item), Numbered).
|
|
130
|
+
|
|
131
|
+
known_kind(Kind) :-
|
|
132
|
+
memberchk(Kind, [property_model, property_contradiction, property_unresolved,
|
|
133
|
+
predicate_model, predicate_contradiction,
|
|
134
|
+
decision_model, decision_contradiction,
|
|
135
|
+
rule_strata, rule_negation_cycle]).
|
|
136
|
+
|
|
137
|
+
certificate_kind(Certificate, Kind) :-
|
|
138
|
+
( get_dict(kind, Certificate, Raw), text_atom(Raw, Kind) -> true ; Kind = unknown ).
|
|
139
|
+
|
|
140
|
+
kind_is(Certificate, Kind) :-
|
|
141
|
+
certificate_kind(Certificate, Kind0),
|
|
142
|
+
Kind0 == Kind.
|
|
143
|
+
|
|
144
|
+
% ------------------------------------------------------------------
|
|
145
|
+
% Property facts
|
|
146
|
+
% ------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
%% certificate_property_contexts(-Contexts)
|
|
149
|
+
% Every comparable context: context(Subject, Property, Scope, Domain, Members)
|
|
150
|
+
% or unresolved(Subject, Property, Scope, Reason). A member is
|
|
151
|
+
% m(FactId, Op, Value, ValidFrom, ValidTo) with the operator already negated
|
|
152
|
+
% for a forbid fact; Domain is int, number, bool, string or set.
|
|
153
|
+
certificate_property_contexts(Contexts) :-
|
|
154
|
+
findall(Fact-tuple(Subject, Property, Op, Type, Value, Unit, Scope, Polarity, From, To),
|
|
155
|
+
( kb:current_req(Req),
|
|
156
|
+
kb:effective_req_property_fact(Req, Subject, Fact, Property, Op, ValueType, Value0, Unit0, Scope, Polarity, From, To),
|
|
157
|
+
kb:comparison_quantity(ValueType, Value0, Unit0, Type, Value, Unit)
|
|
158
|
+
),
|
|
159
|
+
Tuples0),
|
|
160
|
+
sort(1, @<, Tuples0, Tuples),
|
|
161
|
+
findall(Subject-Property, member(_-tuple(Subject, Property, _, _, _, _, _, _, _, _), Tuples), Keys0),
|
|
162
|
+
sort(Keys0, Keys),
|
|
163
|
+
findall(Context,
|
|
164
|
+
( member(Subject-Property, Keys),
|
|
165
|
+
findall(Fact-Tuple, (member(Fact-Tuple, Tuples), Tuple = tuple(Subject, Property, _, _, _, _, _, _, _, _)), Group),
|
|
166
|
+
property_group_context(Subject, Property, Group, Context)
|
|
167
|
+
),
|
|
168
|
+
Contexts).
|
|
169
|
+
|
|
170
|
+
property_group_context(Subject, Property, Group, Context) :-
|
|
171
|
+
findall(Scope, (member(_-tuple(_, _, _, _, _, _, Scope, _, _, _), Group), Scope \== ''), Scopes0),
|
|
172
|
+
sort(Scopes0, Scopes),
|
|
173
|
+
( member(_-tuple(_, _, _, _, _, _, '', _, _, _), Group)
|
|
174
|
+
-> ContextScopes = [''|Scopes]
|
|
175
|
+
; ContextScopes = Scopes
|
|
176
|
+
),
|
|
177
|
+
member(Scope, ContextScopes),
|
|
178
|
+
findall(Fact-Tuple,
|
|
179
|
+
( member(Fact-Tuple, Group),
|
|
180
|
+
Tuple = tuple(_, _, _, _, _, _, FactScope, _, _, _),
|
|
181
|
+
( FactScope == '' -> true ; FactScope == Scope )
|
|
182
|
+
),
|
|
183
|
+
Members0),
|
|
184
|
+
( context_domain(Members0, Domain, Reason)
|
|
185
|
+
-> ( Reason == none
|
|
186
|
+
-> maplist(context_member(Domain), Members0, Members),
|
|
187
|
+
Context = context(Subject, Property, Scope, Domain, Members)
|
|
188
|
+
; Context = unresolved(Subject, Property, Scope, Reason)
|
|
189
|
+
)
|
|
190
|
+
; Context = unresolved(Subject, Property, Scope, "the facts cannot be compared")
|
|
191
|
+
).
|
|
192
|
+
|
|
193
|
+
context_domain(Members, Domain, Reason) :-
|
|
194
|
+
findall(Type, member(_-tuple(_, _, _, Type, _, _, _, _, _, _), Members), Types0),
|
|
195
|
+
sort(Types0, Types),
|
|
196
|
+
( member(_-tuple(_, _, _, _, _, _, _, Polarity, _, _), Members),
|
|
197
|
+
\+ memberchk(Polarity, [require, forbid])
|
|
198
|
+
-> Domain = none, Reason = "a fact has a polarity other than require or forbid"
|
|
199
|
+
; subtract(Types, [int, number], []), Types \== []
|
|
200
|
+
-> ( Types == [int] -> Domain = int ; Domain = number ),
|
|
201
|
+
context_numeric_reason(Members, Reason)
|
|
202
|
+
; Types == [bool]
|
|
203
|
+
-> Domain = bool, context_scalar_reason(Members, Reason)
|
|
204
|
+
; Types == [string]
|
|
205
|
+
-> Domain = string, context_scalar_reason(Members, Reason)
|
|
206
|
+
; Types == [set]
|
|
207
|
+
-> Domain = set, context_scalar_reason(Members, Reason)
|
|
208
|
+
; Types = [_, _|_]
|
|
209
|
+
-> Domain = none, Reason = "the facts use different value types"
|
|
210
|
+
; Domain = none, Reason = "the value type is not supported"
|
|
211
|
+
).
|
|
212
|
+
|
|
213
|
+
context_numeric_reason(Members, Reason) :-
|
|
214
|
+
findall(Unit, (member(_-tuple(_, _, _, _, _, Unit, _, _, _, _), Members), Unit \== ''), Units0),
|
|
215
|
+
sort(Units0, Units),
|
|
216
|
+
( Units = [_, _|_]
|
|
217
|
+
-> Reason = "the facts use different units"
|
|
218
|
+
; member(_-tuple(_, _, Op, _, Value, _, _, _, _, _), Members),
|
|
219
|
+
( \+ memberchk(Op, [eq, neq, lt, lte, gt, gte]) ; \+ number(Value) )
|
|
220
|
+
-> Reason = "a fact has an unsupported operator or a non-numeric value"
|
|
221
|
+
; Reason = none
|
|
222
|
+
).
|
|
223
|
+
|
|
224
|
+
context_scalar_reason(Members, Reason) :-
|
|
225
|
+
( member(_-tuple(_, _, Op, _, _, _, _, _, _, _), Members),
|
|
226
|
+
\+ memberchk(Op, [eq, neq])
|
|
227
|
+
-> Reason = "only eq and neq compare non-numeric values"
|
|
228
|
+
; Reason = none
|
|
229
|
+
).
|
|
230
|
+
|
|
231
|
+
context_member(_Domain, Fact-tuple(_, _, Op0, _, Value, _, _, Polarity, From, To), m(Fact, Op, Value, From, To)) :-
|
|
232
|
+
( Polarity == forbid
|
|
233
|
+
-> numeric_constraint_negation(c(Op0, _, Value), c(Op, _, _))
|
|
234
|
+
; Op = Op0
|
|
235
|
+
).
|
|
236
|
+
|
|
237
|
+
%% certificate_member_holds(+Domain, +Member, +Value)
|
|
238
|
+
% The member's constraint holds for Value.
|
|
239
|
+
certificate_member_holds(Domain, m(_, Op, Bound, _, _), Value) :-
|
|
240
|
+
numeric_domain(Domain),
|
|
241
|
+
!,
|
|
242
|
+
number(Value),
|
|
243
|
+
( Domain == int -> integer(Value) ; true ),
|
|
244
|
+
numeric_constraint_holds(Op, Value, Bound).
|
|
245
|
+
certificate_member_holds(Domain, m(_, Op, Bound, _, _), Value) :-
|
|
246
|
+
scalar_value(Domain, Value),
|
|
247
|
+
text_of(Value, ValueText),
|
|
248
|
+
text_of(Bound, BoundText),
|
|
249
|
+
( Op == eq -> ValueText == BoundText
|
|
250
|
+
; Op == neq -> ValueText \== BoundText
|
|
251
|
+
).
|
|
252
|
+
|
|
253
|
+
% A set value is the exact set of allowed values: eq holds for the same
|
|
254
|
+
% members in any order, neq for any other set.
|
|
255
|
+
certificate_member_holds(set, m(_, Op, Bound, _, _), Value) :-
|
|
256
|
+
set_texts(Value, ValueTexts),
|
|
257
|
+
set_texts(Bound, BoundTexts),
|
|
258
|
+
( Op == eq -> ValueTexts == BoundTexts
|
|
259
|
+
; Op == neq -> ValueTexts \== BoundTexts
|
|
260
|
+
).
|
|
261
|
+
|
|
262
|
+
%% set_texts(+Value, -Texts)
|
|
263
|
+
% The sorted, unique member texts of a set value.
|
|
264
|
+
set_texts(Value, Texts) :-
|
|
265
|
+
is_list(Value),
|
|
266
|
+
maplist(text_of, Value, Texts0),
|
|
267
|
+
sort(Texts0, Texts).
|
|
268
|
+
|
|
269
|
+
numeric_domain(int).
|
|
270
|
+
numeric_domain(number).
|
|
271
|
+
|
|
272
|
+
scalar_value(bool, Value) :- !, text_of(Value, Text), memberchk(Text, ["true", "false"]).
|
|
273
|
+
scalar_value(string, Value) :- ( atom(Value) ; string(Value) ), !.
|
|
274
|
+
|
|
275
|
+
%% certificate_members_satisfiable(+Domain, +Members)
|
|
276
|
+
% Decision procedure used only on a contradiction core: intervals.pl for
|
|
277
|
+
% numbers, direct evaluation over the finite candidates for bool and string.
|
|
278
|
+
certificate_members_satisfiable(Domain, Members) :-
|
|
279
|
+
numeric_domain(Domain),
|
|
280
|
+
!,
|
|
281
|
+
member_constraints(Members, _Variable, Constraints),
|
|
282
|
+
( Domain == int -> Integers = all ; Integers = [] ),
|
|
283
|
+
numeric_constraints_satisfiable(Constraints, Integers).
|
|
284
|
+
certificate_members_satisfiable(Domain, Members) :-
|
|
285
|
+
scalar_candidates(Domain, Members, Candidates),
|
|
286
|
+
member(Value, Candidates),
|
|
287
|
+
forall(member(Member, Members), certificate_member_holds(Domain, Member, Value)),
|
|
288
|
+
!.
|
|
289
|
+
|
|
290
|
+
% One constraint per member, all on the same variable.
|
|
291
|
+
member_constraints([], _Variable, []).
|
|
292
|
+
member_constraints([m(_, Op, Bound, _, _)|Members], Variable, [c(Op, Variable, Bound)|Constraints]) :-
|
|
293
|
+
member_constraints(Members, Variable, Constraints).
|
|
294
|
+
|
|
295
|
+
% Any satisfying value is either a bound some eq names or differs from every
|
|
296
|
+
% bound; one fresh text stands for the latter.
|
|
297
|
+
scalar_candidates(bool, _Members, ["true", "false"]).
|
|
298
|
+
scalar_candidates(string, Members, Candidates) :-
|
|
299
|
+
findall(Text, (member(m(_, _, Bound, _, _), Members), text_of(Bound, Text)), Texts0),
|
|
300
|
+
sort(Texts0, Texts),
|
|
301
|
+
atomic_list_concat(["kibi:fresh"|Texts], '|', FreshAtom),
|
|
302
|
+
atom_string(FreshAtom, Fresh),
|
|
303
|
+
append(Texts, [Fresh], Candidates).
|
|
304
|
+
scalar_candidates(set, Members, Candidates) :-
|
|
305
|
+
findall(Texts, (member(m(_, _, Bound, _, _), Members), set_texts(Bound, Texts)), Sets0),
|
|
306
|
+
sort(Sets0, Sets),
|
|
307
|
+
findall(Text, (member(Set, Sets), member(Text, Set)), Texts0),
|
|
308
|
+
sort(Texts0, Texts),
|
|
309
|
+
atomic_list_concat(["kibi:fresh"|Texts], '|', FreshAtom),
|
|
310
|
+
atom_string(FreshAtom, Fresh),
|
|
311
|
+
append(Sets, [[Fresh]], Candidates).
|
|
312
|
+
|
|
313
|
+
property_theorem(Contexts, Numbered, Theorem, Used, Rejected) :-
|
|
314
|
+
findall(Index-Certificate,
|
|
315
|
+
( member(Index-Certificate, Numbered),
|
|
316
|
+
certificate_kind(Certificate, Kind),
|
|
317
|
+
memberchk(Kind, [property_model, property_contradiction, property_unresolved])
|
|
318
|
+
),
|
|
319
|
+
Candidates),
|
|
320
|
+
foldl(property_context_result(Candidates), Contexts, [], Results),
|
|
321
|
+
theorem_from_results(properties_consistent, Results, Theorem, Used, Rejected0),
|
|
322
|
+
findall(_{certificate: Index, kind: Kind, reason: "certificate names no comparable property context of this KB"},
|
|
323
|
+
( member(Index-Certificate, Candidates),
|
|
324
|
+
\+ memberchk(Index, Used),
|
|
325
|
+
certificate_kind(Certificate, Kind)
|
|
326
|
+
),
|
|
327
|
+
Orphans),
|
|
328
|
+
append(Rejected0, Orphans, Rejected).
|
|
329
|
+
|
|
330
|
+
property_context_result(_Candidates, unresolved(Subject, Property, Scope, Reason), Results,
|
|
331
|
+
[result(unresolved, [], [], _{subjectKey: Subject, propertyKey: Property, scope: Scope, reason: Reason})|Results]) :- !.
|
|
332
|
+
property_context_result(Candidates, context(Subject, Property, Scope, Domain, Members), Results, [Result|Results]) :-
|
|
333
|
+
findall(Index-Certificate,
|
|
334
|
+
( member(Index-Certificate, Candidates),
|
|
335
|
+
certificate_text(Certificate, subjectKey, Subject),
|
|
336
|
+
certificate_text(Certificate, propertyKey, Property),
|
|
337
|
+
certificate_text(Certificate, scope, Scope)
|
|
338
|
+
),
|
|
339
|
+
Matches),
|
|
340
|
+
Label = _{subjectKey: Subject, propertyKey: Property, scope: Scope},
|
|
341
|
+
( Matches == []
|
|
342
|
+
-> Result = result(missing, [], [], Label)
|
|
343
|
+
; Matches = [Index-Certificate]
|
|
344
|
+
-> property_certificate_check(Certificate, Domain, Members, Verdict, Outcome),
|
|
345
|
+
( Outcome == ok
|
|
346
|
+
-> Result = result(Verdict, [Index], [], Label)
|
|
347
|
+
; Outcome = failed(Reason),
|
|
348
|
+
certificate_kind(Certificate, Kind),
|
|
349
|
+
Result = result(missing, [Index], [_{certificate: Index, kind: Kind, reason: Reason}], Label)
|
|
350
|
+
)
|
|
351
|
+
; findall(Index, member(Index-_, Matches), Indexes),
|
|
352
|
+
findall(_{certificate: Index, kind: property, reason: "more than one certificate for the same context"},
|
|
353
|
+
member(Index, Indexes), Failures),
|
|
354
|
+
Result = result(missing, Indexes, Failures, Label)
|
|
355
|
+
).
|
|
356
|
+
|
|
357
|
+
certificate_text(Certificate, Key, Expected) :-
|
|
358
|
+
get_dict(Key, Certificate, Value),
|
|
359
|
+
text_equal(Value, Expected).
|
|
360
|
+
|
|
361
|
+
% property_certificate_check(+Certificate, +Domain, +Members, -Verdict, ?Outcome)
|
|
362
|
+
% Outcome is ok when the certificate re-verifies, else failed(Reason) names
|
|
363
|
+
% the first check that failed.
|
|
364
|
+
property_certificate_check(Certificate, Domain, Members, Verdict, Outcome) :-
|
|
365
|
+
certificate_kind(Certificate, Kind),
|
|
366
|
+
( \+ certificate_text(Certificate, domain, Domain)
|
|
367
|
+
-> Outcome = failed("certificate domain differs from the facts' value domain")
|
|
368
|
+
; Kind == property_model
|
|
369
|
+
-> Verdict = proved,
|
|
370
|
+
model_check(Certificate, Domain, Members, Outcome)
|
|
371
|
+
; Kind == property_contradiction
|
|
372
|
+
-> Verdict = disproved,
|
|
373
|
+
core_check(Certificate, Domain, Members, true, Outcome)
|
|
374
|
+
; Kind == property_unresolved
|
|
375
|
+
-> Verdict = unresolved,
|
|
376
|
+
core_check(Certificate, Domain, Members, false, Outcome)
|
|
377
|
+
).
|
|
378
|
+
|
|
379
|
+
model_check(Certificate, Domain, Members, Outcome) :-
|
|
380
|
+
findall(Fact, member(m(Fact, _, _, _, _), Members), Facts),
|
|
381
|
+
( \+ certificate_id_set(Certificate, facts, Facts)
|
|
382
|
+
-> Outcome = failed("model does not list exactly the facts of its context")
|
|
383
|
+
; \+ get_dict(value, Certificate, _)
|
|
384
|
+
-> Outcome = failed("model has no value")
|
|
385
|
+
; get_dict(value, Certificate, Value),
|
|
386
|
+
member(Member, Members),
|
|
387
|
+
\+ certificate_member_holds(Domain, Member, Value)
|
|
388
|
+
-> Member = m(Fact, _, _, _, _),
|
|
389
|
+
format(string(Reason), "model value does not satisfy ~w", [Fact]),
|
|
390
|
+
Outcome = failed(Reason)
|
|
391
|
+
; Outcome = ok
|
|
392
|
+
).
|
|
393
|
+
|
|
394
|
+
% A contradiction core must be a subset of the context, admit no value, be
|
|
395
|
+
% irreducible (each dropped fact comes with a model of the rest) and, for a
|
|
396
|
+
% contradiction, have validity windows that all overlap. An unresolved
|
|
397
|
+
% certificate is the same core whose windows do not all overlap.
|
|
398
|
+
core_check(Certificate, Domain, Members, WindowsOverlap, Outcome) :-
|
|
399
|
+
( \+ certificate_ids(Certificate, core, _)
|
|
400
|
+
-> Outcome = failed("core is not a list of fact ids")
|
|
401
|
+
; certificate_ids(Certificate, core, Core),
|
|
402
|
+
findall(Member, (member(Fact, Core), member(Member, Members), Member = m(Fact, _, _, _, _)), CoreMembers),
|
|
403
|
+
length(Core, CoreCount),
|
|
404
|
+
length(CoreMembers, CoreCount),
|
|
405
|
+
sort(Core, SortedCore),
|
|
406
|
+
length(SortedCore, CoreCount),
|
|
407
|
+
CoreCount > 0
|
|
408
|
+
-> ( certificate_members_satisfiable(Domain, CoreMembers)
|
|
409
|
+
-> Outcome = failed("core admits a value, so it is not a contradiction")
|
|
410
|
+
; \+ drop_models_valid(Certificate, Domain, CoreMembers)
|
|
411
|
+
-> Outcome = failed("core is not irreducible: a dropped fact has no valid model of the rest")
|
|
412
|
+
; windows_overlap(CoreMembers, Overlap),
|
|
413
|
+
Overlap \== WindowsOverlap
|
|
414
|
+
-> ( WindowsOverlap == true
|
|
415
|
+
-> Outcome = failed("core facts are never valid at the same time")
|
|
416
|
+
; Outcome = failed("core facts are valid at the same time, so the core is a contradiction")
|
|
417
|
+
)
|
|
418
|
+
; Outcome = ok
|
|
419
|
+
)
|
|
420
|
+
; Outcome = failed("core names a fact outside its context or repeats one")
|
|
421
|
+
).
|
|
422
|
+
|
|
423
|
+
drop_models_valid(Certificate, Domain, CoreMembers) :-
|
|
424
|
+
get_dict(dropModels, Certificate, DropModels),
|
|
425
|
+
is_list(DropModels),
|
|
426
|
+
forall(member(m(Fact, _, _, _, _), CoreMembers),
|
|
427
|
+
( member(Drop, DropModels),
|
|
428
|
+
is_dict(Drop),
|
|
429
|
+
certificate_text(Drop, factId, Fact),
|
|
430
|
+
get_dict(value, Drop, Value),
|
|
431
|
+
forall((member(Other, CoreMembers), Other \= m(Fact, _, _, _, _)),
|
|
432
|
+
certificate_member_holds(Domain, Other, Value))
|
|
433
|
+
-> true
|
|
434
|
+
)).
|
|
435
|
+
|
|
436
|
+
windows_overlap(Members, Overlap) :-
|
|
437
|
+
( forall((member(m(_, _, _, FromA, ToA), Members), member(m(_, _, _, FromB, ToB), Members)),
|
|
438
|
+
kb:intervals_overlap(FromA, ToA, FromB, ToB))
|
|
439
|
+
-> Overlap = true
|
|
440
|
+
; Overlap = false
|
|
441
|
+
).
|
|
442
|
+
|
|
443
|
+
certificate_ids(Certificate, Key, Ids) :-
|
|
444
|
+
get_dict(Key, Certificate, Raw),
|
|
445
|
+
is_list(Raw),
|
|
446
|
+
maplist(text_atom, Raw, Ids).
|
|
447
|
+
|
|
448
|
+
certificate_id_set(Certificate, Key, Expected) :-
|
|
449
|
+
certificate_ids(Certificate, Key, Ids),
|
|
450
|
+
msort(Ids, Sorted),
|
|
451
|
+
msort(Expected, Sorted).
|
|
452
|
+
|
|
453
|
+
% ------------------------------------------------------------------
|
|
454
|
+
% Predicate and decision facts
|
|
455
|
+
% ------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
%% certificate_predicate_groups(-Groups)
|
|
458
|
+
% group(Label, Members): the facts that assert or deny one ground atom. A
|
|
459
|
+
% member is p(FactId, Value) with Value true for assert and false for deny.
|
|
460
|
+
certificate_predicate_groups(Groups) :-
|
|
461
|
+
findall(Fact-atom(Namespace, Name, Args, Polarity),
|
|
462
|
+
( kb:current_req(Req),
|
|
463
|
+
kb:effective_req_predicate(Req, Fact, Namespace, Name, Args, Polarity)
|
|
464
|
+
),
|
|
465
|
+
Facts0),
|
|
466
|
+
sort(1, @<, Facts0, Facts),
|
|
467
|
+
findall(Namespace-Name-Args, member(_-atom(Namespace, Name, Args, _), Facts), Keys0),
|
|
468
|
+
sort(Keys0, Keys),
|
|
469
|
+
findall(Group,
|
|
470
|
+
( member(Namespace-Name-Args, Keys),
|
|
471
|
+
findall(Fact-Polarity, member(Fact-atom(Namespace, Name, Args, Polarity), Facts), Pairs),
|
|
472
|
+
Label = _{namespace: Namespace, name: Name, args: Args},
|
|
473
|
+
( member(_-Polarity, Pairs), \+ memberchk(Polarity, [assert, deny])
|
|
474
|
+
-> Group = unresolved(Label, "a fact has a polarity other than assert or deny")
|
|
475
|
+
; findall(p(Fact, Value), (member(Fact-Polarity, Pairs), polarity_value(Polarity, Value)), Members),
|
|
476
|
+
Group = group(Label, Members)
|
|
477
|
+
)
|
|
478
|
+
),
|
|
479
|
+
Groups).
|
|
480
|
+
|
|
481
|
+
polarity_value(assert, true).
|
|
482
|
+
polarity_value(deny, false).
|
|
483
|
+
|
|
484
|
+
%% certificate_decision_groups(-Groups)
|
|
485
|
+
% group(Label, Members): the decision-predicate facts that agree on every key
|
|
486
|
+
% argument. A member is p(FactId, Effect) with Effect allow or deny.
|
|
487
|
+
certificate_decision_groups(Groups) :-
|
|
488
|
+
kb:decision_signatures(Signatures),
|
|
489
|
+
findall(Fact-decision(Namespace, Name, Arity, Key, Effect),
|
|
490
|
+
( kb:current_req(Req),
|
|
491
|
+
kb:effective_req_predicate(Req, Fact, Namespace, Name, Args, Polarity),
|
|
492
|
+
length(Args, Arity),
|
|
493
|
+
memberchk(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition), Signatures),
|
|
494
|
+
findall(Value, (member(Position, KeyPositions), nth1(Position, Args, Value)), Key),
|
|
495
|
+
nth1(DecisionPosition, Args, Decision),
|
|
496
|
+
( kb:effective_decision(Decision, Polarity, Effect0) -> Effect = Effect0 ; Effect = unknown )
|
|
497
|
+
),
|
|
498
|
+
Facts0),
|
|
499
|
+
sort(1, @<, Facts0, Facts),
|
|
500
|
+
findall(Namespace-Name-Arity-Key, member(_-decision(Namespace, Name, Arity, Key, _), Facts), Keys0),
|
|
501
|
+
sort(Keys0, Keys),
|
|
502
|
+
findall(Group,
|
|
503
|
+
( member(Namespace-Name-Arity-Key, Keys),
|
|
504
|
+
findall(p(Fact, Effect), member(Fact-decision(Namespace, Name, Arity, Key, Effect), Facts), Members),
|
|
505
|
+
Label = _{namespace: Namespace, name: Name, arity: Arity, key: Key},
|
|
506
|
+
( memberchk(p(_, unknown), Members)
|
|
507
|
+
-> Group = unresolved(Label, "a decision is neither allow nor deny")
|
|
508
|
+
; Group = group(Label, Members)
|
|
509
|
+
)
|
|
510
|
+
),
|
|
511
|
+
Groups).
|
|
512
|
+
|
|
513
|
+
group_theorem(Theorem, Prefix, Groups, Numbered, Result, Used, Rejected) :-
|
|
514
|
+
atom_concat(Prefix, '_model', ModelKind),
|
|
515
|
+
atom_concat(Prefix, '_contradiction', ContradictionKind),
|
|
516
|
+
findall(Index-Certificate,
|
|
517
|
+
( member(Index-Certificate, Numbered),
|
|
518
|
+
certificate_kind(Certificate, Kind),
|
|
519
|
+
memberchk(Kind, [ModelKind, ContradictionKind])
|
|
520
|
+
),
|
|
521
|
+
Candidates),
|
|
522
|
+
foldl(group_result(ModelKind, ContradictionKind, Candidates), Groups, [], Results),
|
|
523
|
+
theorem_from_results(Theorem, Results, Result, Used, Rejected0),
|
|
524
|
+
findall(_{certificate: Index, kind: Kind, reason: "certificate names no group of this KB"},
|
|
525
|
+
( member(Index-Certificate, Candidates),
|
|
526
|
+
\+ memberchk(Index, Used),
|
|
527
|
+
certificate_kind(Certificate, Kind)
|
|
528
|
+
),
|
|
529
|
+
Orphans),
|
|
530
|
+
append(Rejected0, Orphans, Rejected).
|
|
531
|
+
|
|
532
|
+
group_result(_ModelKind, _ContradictionKind, _Candidates, unresolved(Label, Reason), Results,
|
|
533
|
+
[result(unresolved, [], [], Label.put(reason, Reason))|Results]) :- !.
|
|
534
|
+
group_result(ModelKind, ContradictionKind, Candidates, group(Label, Members), Results, [Result|Results]) :-
|
|
535
|
+
findall(Index-Certificate,
|
|
536
|
+
( member(Index-Certificate, Candidates),
|
|
537
|
+
certificate_matches_label(Certificate, Label)
|
|
538
|
+
),
|
|
539
|
+
Matches),
|
|
540
|
+
( Matches == []
|
|
541
|
+
-> Result = result(missing, [], [], Label)
|
|
542
|
+
; Matches = [Index-Certificate]
|
|
543
|
+
-> group_certificate_check(Certificate, ModelKind, ContradictionKind, Members, Verdict, Outcome),
|
|
544
|
+
( Outcome == ok
|
|
545
|
+
-> Result = result(Verdict, [Index], [], Label)
|
|
546
|
+
; Outcome = failed(Reason),
|
|
547
|
+
certificate_kind(Certificate, Kind),
|
|
548
|
+
Result = result(missing, [Index], [_{certificate: Index, kind: Kind, reason: Reason}], Label)
|
|
549
|
+
)
|
|
550
|
+
; findall(Index, member(Index-_, Matches), Indexes),
|
|
551
|
+
findall(_{certificate: Index, kind: group, reason: "more than one certificate for the same group"},
|
|
552
|
+
member(Index, Indexes), Failures),
|
|
553
|
+
Result = result(missing, Indexes, Failures, Label)
|
|
554
|
+
).
|
|
555
|
+
|
|
556
|
+
certificate_matches_label(Certificate, Label) :-
|
|
557
|
+
forall(get_dict(Key, Label, Expected),
|
|
558
|
+
( get_dict(Key, Certificate, Actual),
|
|
559
|
+
label_value_equal(Actual, Expected)
|
|
560
|
+
)).
|
|
561
|
+
|
|
562
|
+
label_value_equal(Actual, Expected) :-
|
|
563
|
+
is_list(Expected),
|
|
564
|
+
!,
|
|
565
|
+
is_list(Actual),
|
|
566
|
+
maplist(text_equal, Actual, Expected).
|
|
567
|
+
label_value_equal(Actual, Expected) :-
|
|
568
|
+
integer(Expected),
|
|
569
|
+
!,
|
|
570
|
+
Actual == Expected.
|
|
571
|
+
label_value_equal(Actual, Expected) :-
|
|
572
|
+
text_equal(Actual, Expected).
|
|
573
|
+
|
|
574
|
+
group_certificate_check(Certificate, ModelKind, ContradictionKind, Members, Verdict, Outcome) :-
|
|
575
|
+
certificate_kind(Certificate, Kind),
|
|
576
|
+
findall(Fact, member(p(Fact, _), Members), Facts),
|
|
577
|
+
( Kind == ModelKind
|
|
578
|
+
-> Verdict = proved,
|
|
579
|
+
( \+ certificate_id_set(Certificate, facts, Facts)
|
|
580
|
+
-> Outcome = failed("model does not list exactly the facts of its group")
|
|
581
|
+
; \+ get_dict(value, Certificate, _)
|
|
582
|
+
-> Outcome = failed("model has no value")
|
|
583
|
+
; get_dict(value, Certificate, Value),
|
|
584
|
+
text_of(Value, ValueText),
|
|
585
|
+
member(p(Fact, Expected), Members),
|
|
586
|
+
text_of(Expected, ExpectedText),
|
|
587
|
+
ValueText \== ExpectedText
|
|
588
|
+
-> format(string(Reason), "model value does not satisfy ~w", [Fact]),
|
|
589
|
+
Outcome = failed(Reason)
|
|
590
|
+
; Outcome = ok
|
|
591
|
+
)
|
|
592
|
+
; Kind == ContradictionKind
|
|
593
|
+
-> Verdict = disproved,
|
|
594
|
+
( certificate_ids(Certificate, core, [FactA, FactB]),
|
|
595
|
+
memberchk(p(FactA, ValueA), Members),
|
|
596
|
+
memberchk(p(FactB, ValueB), Members),
|
|
597
|
+
ValueA \== ValueB
|
|
598
|
+
-> Outcome = ok
|
|
599
|
+
; Outcome = failed("core is not two facts of the group that disagree")
|
|
600
|
+
)
|
|
601
|
+
).
|
|
602
|
+
|
|
603
|
+
% ------------------------------------------------------------------
|
|
604
|
+
% Rule stratification
|
|
605
|
+
% ------------------------------------------------------------------
|
|
606
|
+
|
|
607
|
+
%% certificate_rule_edges(-Edges)
|
|
608
|
+
% edge(Head, Body, Sign) for every dependency of every stored rule fact, the
|
|
609
|
+
% same graph logic_rules_stratified/1 checks. Head and Body are
|
|
610
|
+
% predicate(Namespace, Name, Arity).
|
|
611
|
+
certificate_rule_edges(Edges) :-
|
|
612
|
+
findall(edge(Head, Body, Sign),
|
|
613
|
+
( kb_entity(_FactId, fact, Props),
|
|
614
|
+
memberchk(fact_kind=RawKind, Props),
|
|
615
|
+
normalize_term_atom(RawKind, rule),
|
|
616
|
+
logic_rule_from_props(Props, rule(rule, _Modality, HeadAtom, BodyExpression, _, _, _, _, _, _)),
|
|
617
|
+
atom_signature(HeadAtom, Head),
|
|
618
|
+
body_dependency(BodyExpression, BodyAtom, Sign),
|
|
619
|
+
atom_signature(BodyAtom, Body)
|
|
620
|
+
),
|
|
621
|
+
Edges0),
|
|
622
|
+
sort(Edges0, Edges).
|
|
623
|
+
|
|
624
|
+
atom_signature(atom(Namespace, Name, Args, _, _), predicate(Namespace, Name, Arity)) :-
|
|
625
|
+
length(Args, Arity).
|
|
626
|
+
|
|
627
|
+
body_dependency(Atom, Atom, positive) :- Atom = atom(_, _, _, _, _).
|
|
628
|
+
body_dependency(not(Atom), Atom, negative).
|
|
629
|
+
body_dependency(all(Items), Atom, Sign) :- member(Item, Items), body_dependency(Item, Atom, Sign).
|
|
630
|
+
body_dependency(any(Items), Atom, Sign) :- member(Item, Items), body_dependency(Item, Atom, Sign).
|
|
631
|
+
body_dependency(count(Atom, _, _), Atom, positive).
|
|
632
|
+
|
|
633
|
+
rules_theorem(Edges, Numbered, Theorem, Used, Rejected) :-
|
|
634
|
+
findall(Index-Certificate,
|
|
635
|
+
( member(Index-Certificate, Numbered),
|
|
636
|
+
certificate_kind(Certificate, Kind),
|
|
637
|
+
memberchk(Kind, [rule_strata, rule_negation_cycle])
|
|
638
|
+
),
|
|
639
|
+
Candidates),
|
|
640
|
+
findall(Signature, (member(edge(A, B, _), Edges), member(Signature, [A, B])), Signatures0),
|
|
641
|
+
sort(Signatures0, Signatures),
|
|
642
|
+
length(Signatures, PredicateCount),
|
|
643
|
+
( Candidates == []
|
|
644
|
+
-> ( Edges == []
|
|
645
|
+
-> Verdict = proved, Used = [], Rejected = []
|
|
646
|
+
; Verdict = unresolved, Used = [],
|
|
647
|
+
Rejected = [_{certificate: 0, kind: rule_strata, reason: "no stratification certificate for the stored rules"}]
|
|
648
|
+
)
|
|
649
|
+
; Candidates = [Index-Certificate]
|
|
650
|
+
-> Used = [Index],
|
|
651
|
+
rule_certificate_check(Certificate, Edges, Signatures, Verdict0, Outcome),
|
|
652
|
+
( Outcome == ok
|
|
653
|
+
-> Verdict = Verdict0, Rejected = []
|
|
654
|
+
; Outcome = failed(Reason),
|
|
655
|
+
certificate_kind(Certificate, Kind),
|
|
656
|
+
Verdict = unresolved,
|
|
657
|
+
Rejected = [_{certificate: Index, kind: Kind, reason: Reason}]
|
|
658
|
+
)
|
|
659
|
+
; findall(Index, member(Index-_, Candidates), Used),
|
|
660
|
+
Verdict = unresolved,
|
|
661
|
+
findall(_{certificate: Index, kind: rule_strata, reason: "more than one stratification certificate"},
|
|
662
|
+
member(Index, Used), Rejected)
|
|
663
|
+
),
|
|
664
|
+
length(Edges, EdgeCount),
|
|
665
|
+
Theorem = _{theorem: rules_stratified, verdict: Verdict, predicates: PredicateCount, edges: EdgeCount}.
|
|
666
|
+
|
|
667
|
+
rule_certificate_check(Certificate, Edges, Signatures, Verdict, Outcome) :-
|
|
668
|
+
certificate_kind(Certificate, Kind),
|
|
669
|
+
( Kind == rule_strata
|
|
670
|
+
-> Verdict = proved,
|
|
671
|
+
( get_dict(strata, Certificate, Raw), is_list(Raw),
|
|
672
|
+
maplist(stratum_entry, Raw, Entries)
|
|
673
|
+
-> ( member(Signature, Signatures), \+ memberchk(Signature-_, Entries)
|
|
674
|
+
-> Outcome = failed("strata omit a predicate the rules depend on")
|
|
675
|
+
; member(edge(Head, Body, Sign), Edges),
|
|
676
|
+
memberchk(Head-HeadStratum, Entries),
|
|
677
|
+
memberchk(Body-BodyStratum, Entries),
|
|
678
|
+
\+ stratum_edge_ok(Sign, HeadStratum, BodyStratum)
|
|
679
|
+
-> Outcome = failed("a rule dependency violates the stratum order")
|
|
680
|
+
; Outcome = ok
|
|
681
|
+
)
|
|
682
|
+
; Outcome = failed("strata are not a list of predicates with integer strata")
|
|
683
|
+
)
|
|
684
|
+
; Kind == rule_negation_cycle
|
|
685
|
+
-> Verdict = disproved,
|
|
686
|
+
( get_dict(cycle, Certificate, Raw), is_list(Raw), Raw \== [],
|
|
687
|
+
maplist(cycle_edge, Raw, Cycle),
|
|
688
|
+
forall(member(Edge, Cycle), memberchk(Edge, Edges)),
|
|
689
|
+
memberchk(edge(_, _, negative), Cycle),
|
|
690
|
+
cycle_closed(Cycle)
|
|
691
|
+
-> Outcome = ok
|
|
692
|
+
; Outcome = failed("cycle is not a closed path of stored rule dependencies with a negated edge")
|
|
693
|
+
)
|
|
694
|
+
).
|
|
695
|
+
|
|
696
|
+
stratum_entry(Entry, predicate(Namespace, Name, Arity)-Stratum) :-
|
|
697
|
+
is_dict(Entry),
|
|
698
|
+
get_dict(namespace, Entry, NamespaceRaw), text_atom(NamespaceRaw, Namespace),
|
|
699
|
+
get_dict(name, Entry, NameRaw), text_atom(NameRaw, Name),
|
|
700
|
+
get_dict(arity, Entry, Arity), integer(Arity),
|
|
701
|
+
get_dict(stratum, Entry, Stratum), integer(Stratum), Stratum >= 0.
|
|
702
|
+
|
|
703
|
+
stratum_edge_ok(positive, Head, Body) :- Head >= Body.
|
|
704
|
+
stratum_edge_ok(negative, Head, Body) :- Head > Body.
|
|
705
|
+
|
|
706
|
+
cycle_edge(Entry, edge(Head, Body, Sign)) :-
|
|
707
|
+
is_dict(Entry),
|
|
708
|
+
get_dict(head, Entry, HeadRaw), signature_from_dict(HeadRaw, Head),
|
|
709
|
+
get_dict(body, Entry, BodyRaw), signature_from_dict(BodyRaw, Body),
|
|
710
|
+
get_dict(sign, Entry, SignRaw), text_atom(SignRaw, Sign).
|
|
711
|
+
|
|
712
|
+
signature_from_dict(Dict, predicate(Namespace, Name, Arity)) :-
|
|
713
|
+
is_dict(Dict),
|
|
714
|
+
get_dict(namespace, Dict, NamespaceRaw), text_atom(NamespaceRaw, Namespace),
|
|
715
|
+
get_dict(name, Dict, NameRaw), text_atom(NameRaw, Name),
|
|
716
|
+
get_dict(arity, Dict, Arity), integer(Arity).
|
|
717
|
+
|
|
718
|
+
% Consecutive edges chain head to body (a head depends on its body) and the
|
|
719
|
+
% last edge's body is the first edge's head.
|
|
720
|
+
cycle_closed([edge(First, Next, _)|Rest]) :-
|
|
721
|
+
cycle_chain(Rest, Next, First).
|
|
722
|
+
|
|
723
|
+
cycle_chain([], Last, First) :- Last == First.
|
|
724
|
+
cycle_chain([edge(Head, Body, _)|Rest], Previous, First) :-
|
|
725
|
+
Head == Previous,
|
|
726
|
+
cycle_chain(Rest, Body, First).
|
|
727
|
+
|
|
728
|
+
% ------------------------------------------------------------------
|
|
729
|
+
% Theorem summaries
|
|
730
|
+
% ------------------------------------------------------------------
|
|
731
|
+
|
|
732
|
+
% result(Verdict, CertificateIndexes, Rejected, Label), Verdict one of
|
|
733
|
+
% proved, disproved, unresolved, missing. A theorem is disproved by any
|
|
734
|
+
% re-verified contradiction, unresolved while any context is unresolved or
|
|
735
|
+
% missing, and proved only when every context has a re-verified model.
|
|
736
|
+
theorem_from_results(Theorem, Results, Summary, Used, Rejected) :-
|
|
737
|
+
findall(Index, (member(result(_, Indexes, _, _), Results), member(Index, Indexes)), Used),
|
|
738
|
+
findall(Failure, (member(result(_, _, Failures, _), Results), member(Failure, Failures)), Rejected1),
|
|
739
|
+
findall(Label, member(result(missing, [], [], Label), Results), MissingLabels),
|
|
740
|
+
missing_reasons(MissingLabels, MissingRejected),
|
|
741
|
+
append(Rejected1, MissingRejected, Rejected),
|
|
742
|
+
count_verdict(proved, Results, Proved),
|
|
743
|
+
count_verdict(disproved, Results, Disproved),
|
|
744
|
+
findall(Label, member(result(unresolved, _, _, Label), Results), Unresolved),
|
|
745
|
+
findall(Label, member(result(disproved, _, _, Label), Results), Contradictions),
|
|
746
|
+
length(Results, Total),
|
|
747
|
+
( Disproved > 0 -> Verdict = disproved
|
|
748
|
+
; Proved =:= Total -> Verdict = proved
|
|
749
|
+
; Verdict = unresolved
|
|
750
|
+
),
|
|
751
|
+
Summary = _{
|
|
752
|
+
theorem: Theorem,
|
|
753
|
+
verdict: Verdict,
|
|
754
|
+
groups: Total,
|
|
755
|
+
models: Proved,
|
|
756
|
+
contradictions: Contradictions,
|
|
757
|
+
unresolved: Unresolved
|
|
758
|
+
}.
|
|
759
|
+
|
|
760
|
+
missing_reasons(Labels, Rejected) :-
|
|
761
|
+
findall(_{certificate: 0, kind: missing, reason: Reason, group: Label},
|
|
762
|
+
( member(Label, Labels),
|
|
763
|
+
Reason = "no certificate for this group"
|
|
764
|
+
),
|
|
765
|
+
Rejected).
|
|
766
|
+
|
|
767
|
+
count_verdict(Verdict, Results, Count) :-
|
|
768
|
+
aggregate_all(count, member(result(Verdict, _, _, _), Results), Count).
|
|
769
|
+
|
|
770
|
+
% ------------------------------------------------------------------
|
|
771
|
+
% Text helpers: JSON gives strings, the KB gives atoms.
|
|
772
|
+
% ------------------------------------------------------------------
|
|
773
|
+
|
|
774
|
+
text_of(Value, Text) :- string(Value), !, Text = Value.
|
|
775
|
+
text_of(Value, Text) :- atom(Value), !, atom_string(Value, Text).
|
|
776
|
+
text_of(Value, Text) :- number(Value), !, number_string(Value, Text).
|
|
777
|
+
|
|
778
|
+
text_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
779
|
+
text_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|
|
780
|
+
|
|
781
|
+
text_equal(Left, Right) :-
|
|
782
|
+
text_of(Left, Text),
|
|
783
|
+
text_of(Right, Text).
|