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/discovery.pl
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
requirement_proof_json/5,
|
|
12
12
|
requirement_proof_json/7,
|
|
13
13
|
symbol_proof_json/7,
|
|
14
|
-
graph_expand_json/8
|
|
14
|
+
graph_expand_json/8,
|
|
15
|
+
search_answer_verdicts_json/2
|
|
15
16
|
]).
|
|
16
17
|
|
|
17
18
|
% Source contains non-ASCII text; do not depend on the host locale.
|
|
@@ -25,7 +26,9 @@
|
|
|
25
26
|
:- use_module('kb.pl').
|
|
26
27
|
:- use_module(library(semweb/rdf_db), [rdf_generation/1]).
|
|
27
28
|
:- use_module('requirement_proof.pl', [requirement_proof_context/1, requirement_proof_context/4, requirement_proof_context/6, requirement_proof/4]).
|
|
28
|
-
:- use_module('
|
|
29
|
+
:- use_module('checks.pl', [scenario_feasibility_outcome/2, infeasible_scenario/5]).
|
|
30
|
+
:- use_module('logic_ir.pl', [logic_rule_conflict/3, logic_rule_conflict/4]).
|
|
31
|
+
:- use_module('status.pl', [status_meta_dict/1, status_scope_dict/1]).
|
|
29
32
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
30
33
|
|
|
31
34
|
find_gaps_json(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Limit, Offset, JsonString) :-
|
|
@@ -99,6 +102,214 @@ symbol_explain_role(SymbolId, Role) :-
|
|
|
99
102
|
!.
|
|
100
103
|
symbol_explain_role(_SymbolId, unknown).
|
|
101
104
|
|
|
105
|
+
%% search_answer_verdicts_json(+ReqIds, -JsonString)
|
|
106
|
+
% implements REQ-kibi-search-answer-layer-v2
|
|
107
|
+
% What the existing checks report about the requirements a kb_search answer
|
|
108
|
+
% names, in one engine round trip and bounded to those requirements:
|
|
109
|
+
% contradictions strict-property and predicate witnesses from
|
|
110
|
+
% kb:req_conflict_witness/3, and rule witnesses from the
|
|
111
|
+
% pair generator and rule comparison the
|
|
112
|
+
% domain-contradictions check uses (disjoint pairs omitted)
|
|
113
|
+
% scenarios scenario_feasibility_outcome/2 of each scenario the
|
|
114
|
+
% requirement specifies
|
|
115
|
+
% forbids other scenarios infeasible_scenario/5 says it forbids
|
|
116
|
+
% inventory the semantic-inventory stage of the proof ladder, with
|
|
117
|
+
% the propositions still ambiguous or ontology gaps
|
|
118
|
+
% Whole-KB scans (check_domain_contradiction_witnesses/1) are deliberately
|
|
119
|
+
% not used: they take seconds on a large KB. The functional-predicate
|
|
120
|
+
% declarations a rule comparison reads are collected once per call rather
|
|
121
|
+
% than once per compared pair, which dominated the cost for requirements with
|
|
122
|
+
% many rules. A requirement whose checks raise an error reports that error
|
|
123
|
+
% instead of failing the answer. Scope names the snapshot the verdicts were
|
|
124
|
+
% computed from.
|
|
125
|
+
search_answer_verdicts_json(ReqIds, JsonString) :-
|
|
126
|
+
search_answer_functional(Functional),
|
|
127
|
+
maplist(search_answer_verdict(Functional), ReqIds, Verdicts),
|
|
128
|
+
status_scope_dict(Scope),
|
|
129
|
+
dict_json_string(_{requirements: Verdicts, scope: Scope}, JsonString).
|
|
130
|
+
|
|
131
|
+
% The declarations logic_rule_conflict/3 would read for every pair; when they
|
|
132
|
+
% cannot be read here, each comparison reads them itself.
|
|
133
|
+
search_answer_functional(some(Functional)) :-
|
|
134
|
+
catch(logic_ir:functional_predicate_declarations(Functional), _, fail),
|
|
135
|
+
!.
|
|
136
|
+
search_answer_functional(none).
|
|
137
|
+
|
|
138
|
+
search_answer_rule_status(some(Functional), RuleA, RuleB, Status) :-
|
|
139
|
+
!,
|
|
140
|
+
logic_rule_conflict(RuleA, RuleB, Functional, Status).
|
|
141
|
+
search_answer_rule_status(none, RuleA, RuleB, Status) :-
|
|
142
|
+
logic_rule_conflict(RuleA, RuleB, Status).
|
|
143
|
+
|
|
144
|
+
search_answer_verdict(Functional, ReqId, Verdict) :-
|
|
145
|
+
catch(search_answer_verdict_(Functional, ReqId, Verdict0), Error, true),
|
|
146
|
+
( var(Error)
|
|
147
|
+
-> Verdict = Verdict0
|
|
148
|
+
; format(string(Text0), "~q", [Error]),
|
|
149
|
+
search_answer_clip(Text0, 300, Text),
|
|
150
|
+
Verdict = _{id: ReqId, error: Text}
|
|
151
|
+
).
|
|
152
|
+
|
|
153
|
+
search_answer_verdict_(Functional, ReqId, _{
|
|
154
|
+
id: ReqId,
|
|
155
|
+
contradictions: Contradictions,
|
|
156
|
+
scenarios: Scenarios,
|
|
157
|
+
forbids: Forbids,
|
|
158
|
+
inventory: Inventory
|
|
159
|
+
}) :-
|
|
160
|
+
findall(C, search_answer_contradiction(Functional, ReqId, C), Contradictions0),
|
|
161
|
+
sort(Contradictions0, Contradictions),
|
|
162
|
+
findall(S, search_answer_scenario_outcome(ReqId, S), Scenarios0),
|
|
163
|
+
sort(Scenarios0, Scenarios),
|
|
164
|
+
findall(F, search_answer_forbidden_scenario(ReqId, F), Forbids0),
|
|
165
|
+
sort(Forbids0, Forbids),
|
|
166
|
+
search_answer_inventory(ReqId, Inventory).
|
|
167
|
+
|
|
168
|
+
search_answer_contradiction(_Functional, ReqId, _{
|
|
169
|
+
kind: Kind,
|
|
170
|
+
status: Status,
|
|
171
|
+
with: Other,
|
|
172
|
+
facts: Facts,
|
|
173
|
+
reason: Reason
|
|
174
|
+
}) :-
|
|
175
|
+
( req_conflict_witness(ReqId, Other, Witness)
|
|
176
|
+
; req_conflict_witness(Other, ReqId, Witness)
|
|
177
|
+
),
|
|
178
|
+
get_dict(kind, Witness, Kind),
|
|
179
|
+
get_dict(status, Witness, Status),
|
|
180
|
+
get_dict(reason, Witness, RawReason),
|
|
181
|
+
search_answer_text(RawReason, Reason),
|
|
182
|
+
findall(FactId,
|
|
183
|
+
( member(SideKey, [left, right]),
|
|
184
|
+
get_dict(SideKey, Witness, Side),
|
|
185
|
+
get_dict(factId, Side, FactId)
|
|
186
|
+
),
|
|
187
|
+
Facts).
|
|
188
|
+
search_answer_contradiction(Functional, ReqId, _{
|
|
189
|
+
kind: rule,
|
|
190
|
+
status: Status,
|
|
191
|
+
with: Other,
|
|
192
|
+
facts: [FactA, FactB],
|
|
193
|
+
reason: Reason
|
|
194
|
+
}) :-
|
|
195
|
+
% The check orders each pair; compare it in that order and only then
|
|
196
|
+
% orient the result towards ReqId.
|
|
197
|
+
( checks:opposing_rule_requirement_facts(ReqId, Other, FactA, FactB, RuleLeft, RuleRight),
|
|
198
|
+
LeftReq = ReqId, RightReq = Other
|
|
199
|
+
; checks:opposing_rule_requirement_facts(Other, ReqId, FactB, FactA, RuleLeft, RuleRight),
|
|
200
|
+
LeftReq = Other, RightReq = ReqId
|
|
201
|
+
),
|
|
202
|
+
search_answer_rule_status(Functional, RuleLeft, RuleRight, Status),
|
|
203
|
+
Status \== disjoint,
|
|
204
|
+
format(string(Reason), "Rule conflict (~w) between ~w and ~w", [Status, LeftReq, RightReq]).
|
|
205
|
+
|
|
206
|
+
search_answer_scenario_outcome(ReqId, Outcome) :-
|
|
207
|
+
kb_relationship(specified_by, ReqId, ScenarioId),
|
|
208
|
+
scenario_feasibility_outcome(ScenarioId, Result),
|
|
209
|
+
search_answer_outcome(ScenarioId, Result, Outcome).
|
|
210
|
+
|
|
211
|
+
search_answer_outcome(ScenarioId, infeasible(witness(ReqIds, AssumedFacts, ReqFacts, RawReason)), _{
|
|
212
|
+
scenario: ScenarioId,
|
|
213
|
+
outcome: infeasible,
|
|
214
|
+
requirement: ReqId,
|
|
215
|
+
requirements: ReqIds,
|
|
216
|
+
assumed: AssumedFacts,
|
|
217
|
+
fact: ReqFacts,
|
|
218
|
+
reason: Reason
|
|
219
|
+
}) :-
|
|
220
|
+
!,
|
|
221
|
+
( ReqIds = [ReqId|_] -> true ; ReqId = '' ),
|
|
222
|
+
search_answer_text(RawReason, Reason).
|
|
223
|
+
search_answer_outcome(ScenarioId, unknown(Reason), _{
|
|
224
|
+
scenario: ScenarioId,
|
|
225
|
+
outcome: unknown,
|
|
226
|
+
reason: Reason,
|
|
227
|
+
facts: []
|
|
228
|
+
}) :-
|
|
229
|
+
atom(Reason),
|
|
230
|
+
!.
|
|
231
|
+
search_answer_outcome(ScenarioId, unknown(Compound), _{
|
|
232
|
+
scenario: ScenarioId,
|
|
233
|
+
outcome: unknown,
|
|
234
|
+
reason: Reason,
|
|
235
|
+
facts: Ids
|
|
236
|
+
}) :-
|
|
237
|
+
compound(Compound),
|
|
238
|
+
Compound =.. [Reason, Ids0],
|
|
239
|
+
is_list(Ids0),
|
|
240
|
+
!,
|
|
241
|
+
Ids = Ids0.
|
|
242
|
+
search_answer_outcome(ScenarioId, not_applicable(Compound), _{
|
|
243
|
+
scenario: ScenarioId,
|
|
244
|
+
outcome: not_applicable,
|
|
245
|
+
reason: Reason,
|
|
246
|
+
facts: Ids
|
|
247
|
+
}) :-
|
|
248
|
+
compound(Compound),
|
|
249
|
+
Compound =.. [Reason, Ids],
|
|
250
|
+
is_list(Ids),
|
|
251
|
+
!.
|
|
252
|
+
search_answer_outcome(ScenarioId, Result, _{scenario: ScenarioId, outcome: Outcome}) :-
|
|
253
|
+
( atom(Result)
|
|
254
|
+
-> Outcome = Result
|
|
255
|
+
; format(atom(Outcome), "~q", [Result])
|
|
256
|
+
).
|
|
257
|
+
|
|
258
|
+
search_answer_forbidden_scenario(ReqId, _{
|
|
259
|
+
scenario: ScenarioId,
|
|
260
|
+
assumed: AssumedFacts,
|
|
261
|
+
fact: ReqFacts,
|
|
262
|
+
reason: Reason
|
|
263
|
+
}) :-
|
|
264
|
+
infeasible_scenario(ScenarioId, ReqIds, AssumedFacts, ReqFacts, RawReason),
|
|
265
|
+
memberchk(ReqId, ReqIds),
|
|
266
|
+
\+ kb_relationship(specified_by, ReqId, ScenarioId),
|
|
267
|
+
search_answer_text(RawReason, Reason).
|
|
268
|
+
|
|
269
|
+
search_answer_inventory(ReqId, _{
|
|
270
|
+
status: Status,
|
|
271
|
+
propositionCount: Count,
|
|
272
|
+
unresolved: Unresolved
|
|
273
|
+
}) :-
|
|
274
|
+
kb_entity(ReqId, req, Props),
|
|
275
|
+
!,
|
|
276
|
+
requirement_proof:semantic_inventory_stage(Props, Stage, Entries),
|
|
277
|
+
get_dict(status, Stage, Status),
|
|
278
|
+
get_dict(propositionCount, Stage, Count),
|
|
279
|
+
findall(_{claim: Claim, status: EntryStatus},
|
|
280
|
+
( member(Entry, Entries),
|
|
281
|
+
requirement_proof:inventory_entry_unresolved(Entry),
|
|
282
|
+
requirement_proof:inventory_entry_status(Entry, EntryStatus),
|
|
283
|
+
requirement_proof:inventory_entry_field(Entry, claim_text, RawClaim),
|
|
284
|
+
search_answer_text(RawClaim, Claim0),
|
|
285
|
+
search_answer_clip(Claim0, 300, Claim)
|
|
286
|
+
),
|
|
287
|
+
Unresolved).
|
|
288
|
+
search_answer_inventory(_ReqId, _{status: unknown, propositionCount: 0, unresolved: []}).
|
|
289
|
+
|
|
290
|
+
search_answer_text(^^(Value, _Type), Text) :-
|
|
291
|
+
!,
|
|
292
|
+
search_answer_text(Value, Text).
|
|
293
|
+
search_answer_text(Value, Text) :-
|
|
294
|
+
string(Value),
|
|
295
|
+
!,
|
|
296
|
+
Text = Value.
|
|
297
|
+
search_answer_text(Value, Text) :-
|
|
298
|
+
atom(Value),
|
|
299
|
+
!,
|
|
300
|
+
atom_string(Value, Text).
|
|
301
|
+
search_answer_text(Value, Text) :-
|
|
302
|
+
format(string(Text), "~w", [Value]).
|
|
303
|
+
|
|
304
|
+
search_answer_clip(Text, Max, Clipped) :-
|
|
305
|
+
string_length(Text, Length),
|
|
306
|
+
( Length =< Max
|
|
307
|
+
-> Clipped = Text
|
|
308
|
+
; Keep is Max - 1,
|
|
309
|
+
sub_string(Text, 0, Keep, _, Prefix),
|
|
310
|
+
string_concat(Prefix, "…", Clipped)
|
|
311
|
+
).
|
|
312
|
+
|
|
102
313
|
% Slim per-requirement proof evidence for quality diagnostics (W1 push-down).
|
|
103
314
|
% Projects only the fields the quality diagnostics consume - proof status,
|
|
104
315
|
% the passing-e2e stage summary, and receipt-related gap codes - instead of
|
|
@@ -167,7 +378,7 @@ coverage_report_json(By, Tags, IncludePassing, BindingMode, TestBindings, Includ
|
|
|
167
378
|
Response = _{summary: Summary, rows: Rows, meta: Meta},
|
|
168
379
|
dict_json_string(Response, JsonString).
|
|
169
380
|
|
|
170
|
-
% implements REQ-kibi-
|
|
381
|
+
% implements REQ-kibi-conservative-requirement-proof
|
|
171
382
|
% Proof-status filtered report. The status filter selects requirement rows by
|
|
172
383
|
% proofStatus (proven, missing, unresolved, not_applicable) and replaces the
|
|
173
384
|
% default include-passing filtering, so callers can enumerate exactly one
|
package/src/intervals.pl
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
% implements REQ-kibi-truthful-consistency
|
|
2
|
+
%
|
|
3
|
+
% Exact satisfiability for one-variable numeric comparisons.
|
|
4
|
+
%
|
|
5
|
+
% A constraint is c(Op, Variable, Value) where Op is one of eq, neq, lt, lte,
|
|
6
|
+
% gt, gte and Value is a number. Variables are ordinary Prolog variables and
|
|
7
|
+
% are compared by identity, never bound. Each variable ranges over the reals
|
|
8
|
+
% unless the caller names it as integer-valued; the conjunction is
|
|
9
|
+
% satisfiable exactly when every variable's set is non-empty. Strict bounds,
|
|
10
|
+
% non-integer bounds on integer variables and excluded points are handled
|
|
11
|
+
% without scaling or labelling, so the answer is exact and terminates in
|
|
12
|
+
% linear time.
|
|
13
|
+
:- module(intervals, [
|
|
14
|
+
numeric_constraints_satisfiable/1,
|
|
15
|
+
numeric_constraints_satisfiable/2,
|
|
16
|
+
numeric_constraint_entailed/2,
|
|
17
|
+
numeric_constraint_entailed/3,
|
|
18
|
+
numeric_constraint_negation/2,
|
|
19
|
+
numeric_constraint_holds/3
|
|
20
|
+
]).
|
|
21
|
+
|
|
22
|
+
%% numeric_constraints_satisfiable(+Constraints)
|
|
23
|
+
% True when some assignment of reals satisfies every constraint.
|
|
24
|
+
numeric_constraints_satisfiable(Constraints) :-
|
|
25
|
+
numeric_constraints_satisfiable(Constraints, []).
|
|
26
|
+
|
|
27
|
+
%% numeric_constraints_satisfiable(+Constraints, +IntegerVariables)
|
|
28
|
+
% As numeric_constraints_satisfiable/1, but every variable in
|
|
29
|
+
% IntegerVariables (compared by identity) ranges over the integers: no
|
|
30
|
+
% integer lies strictly between 0 and 1, and eq 0.5 has no integer solution.
|
|
31
|
+
% IntegerVariables may also be the atom `all`, making every variable integral.
|
|
32
|
+
numeric_constraints_satisfiable(Constraints, IntegerVariables) :-
|
|
33
|
+
term_variables(Constraints, Variables),
|
|
34
|
+
forall(
|
|
35
|
+
member(Variable, Variables),
|
|
36
|
+
( integer_variable(Variable, IntegerVariables)
|
|
37
|
+
-> integer_variable_feasible(Variable, Constraints)
|
|
38
|
+
; variable_feasible(Variable, Constraints)
|
|
39
|
+
)
|
|
40
|
+
).
|
|
41
|
+
|
|
42
|
+
%% numeric_constraint_entailed(+Premises, +Constraint)
|
|
43
|
+
% True when every assignment that satisfies Premises satisfies Constraint.
|
|
44
|
+
numeric_constraint_entailed(Premises, Constraint) :-
|
|
45
|
+
numeric_constraint_entailed(Premises, Constraint, []).
|
|
46
|
+
|
|
47
|
+
%% numeric_constraint_entailed(+Premises, +Constraint, +IntegerVariables)
|
|
48
|
+
numeric_constraint_entailed(Premises, Constraint, IntegerVariables) :-
|
|
49
|
+
numeric_constraint_negation(Constraint, Negated),
|
|
50
|
+
\+ numeric_constraints_satisfiable([Negated|Premises], IntegerVariables).
|
|
51
|
+
|
|
52
|
+
integer_variable(_, all) :- !.
|
|
53
|
+
integer_variable(Variable, IntegerVariables) :-
|
|
54
|
+
is_list(IntegerVariables),
|
|
55
|
+
member(Candidate, IntegerVariables),
|
|
56
|
+
Candidate == Variable,
|
|
57
|
+
!.
|
|
58
|
+
|
|
59
|
+
%% numeric_constraint_negation(+Constraint, -Negated)
|
|
60
|
+
numeric_constraint_negation(c(Op, Variable, Value), c(Negated, Variable, Value)) :-
|
|
61
|
+
negated_operator(Op, Negated).
|
|
62
|
+
|
|
63
|
+
negated_operator(eq, neq).
|
|
64
|
+
negated_operator(neq, eq).
|
|
65
|
+
negated_operator(lt, gte).
|
|
66
|
+
negated_operator(gte, lt).
|
|
67
|
+
negated_operator(lte, gt).
|
|
68
|
+
negated_operator(gt, lte).
|
|
69
|
+
|
|
70
|
+
%% numeric_constraint_holds(+Op, +Left, +Right)
|
|
71
|
+
% Evaluate a comparison between two numbers.
|
|
72
|
+
numeric_constraint_holds(eq, Left, Right) :- Left =:= Right.
|
|
73
|
+
numeric_constraint_holds(neq, Left, Right) :- Left =\= Right.
|
|
74
|
+
numeric_constraint_holds(lt, Left, Right) :- Left < Right.
|
|
75
|
+
numeric_constraint_holds(lte, Left, Right) :- Left =< Right.
|
|
76
|
+
numeric_constraint_holds(gt, Left, Right) :- Left > Right.
|
|
77
|
+
numeric_constraint_holds(gte, Left, Right) :- Left >= Right.
|
|
78
|
+
|
|
79
|
+
variable_feasible(Variable, Constraints) :-
|
|
80
|
+
findall(Op-Value, (member(c(Op, Other, Value), Constraints), Other == Variable), Bounds),
|
|
81
|
+
foldl(apply_bound, Bounds, interval(ninf, open, pinf, open, []), Interval),
|
|
82
|
+
interval_non_empty(Interval).
|
|
83
|
+
|
|
84
|
+
% interval(Low, LowKind, High, HighKind, Excluded)
|
|
85
|
+
apply_bound(eq-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo1, LoK1, Hi1, HiK1, Ex)) :-
|
|
86
|
+
raise_low(Lo, LoK, Value, closed, Lo1, LoK1),
|
|
87
|
+
lower_high(Hi, HiK, Value, closed, Hi1, HiK1).
|
|
88
|
+
apply_bound(neq-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo, LoK, Hi, HiK, [Value|Ex])).
|
|
89
|
+
apply_bound(lt-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo, LoK, Hi1, HiK1, Ex)) :-
|
|
90
|
+
lower_high(Hi, HiK, Value, open, Hi1, HiK1).
|
|
91
|
+
apply_bound(lte-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo, LoK, Hi1, HiK1, Ex)) :-
|
|
92
|
+
lower_high(Hi, HiK, Value, closed, Hi1, HiK1).
|
|
93
|
+
apply_bound(gt-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo1, LoK1, Hi, HiK, Ex)) :-
|
|
94
|
+
raise_low(Lo, LoK, Value, open, Lo1, LoK1).
|
|
95
|
+
apply_bound(gte-Value, interval(Lo, LoK, Hi, HiK, Ex), interval(Lo1, LoK1, Hi, HiK, Ex)) :-
|
|
96
|
+
raise_low(Lo, LoK, Value, closed, Lo1, LoK1).
|
|
97
|
+
|
|
98
|
+
raise_low(ninf, _, Value, Kind, Value, Kind) :- !.
|
|
99
|
+
raise_low(Lo, _, Value, Kind, Value, Kind) :- Value > Lo, !.
|
|
100
|
+
raise_low(Lo, LoK, Value, Kind, Lo, Kind1) :- Value =:= Lo, !, tighter_kind(LoK, Kind, Kind1).
|
|
101
|
+
raise_low(Lo, LoK, _, _, Lo, LoK).
|
|
102
|
+
|
|
103
|
+
lower_high(pinf, _, Value, Kind, Value, Kind) :- !.
|
|
104
|
+
lower_high(Hi, _, Value, Kind, Value, Kind) :- Value < Hi, !.
|
|
105
|
+
lower_high(Hi, HiK, Value, Kind, Hi, Kind1) :- Value =:= Hi, !, tighter_kind(HiK, Kind, Kind1).
|
|
106
|
+
lower_high(Hi, HiK, _, _, Hi, HiK).
|
|
107
|
+
|
|
108
|
+
tighter_kind(closed, closed, closed) :- !.
|
|
109
|
+
tighter_kind(_, _, open).
|
|
110
|
+
|
|
111
|
+
% An unbounded or non-degenerate real interval cannot be emptied by finitely
|
|
112
|
+
% many excluded points. A degenerate interval is a single closed point.
|
|
113
|
+
interval_non_empty(interval(ninf, _, _, _, _)) :- !.
|
|
114
|
+
interval_non_empty(interval(_, _, pinf, _, _)) :- !.
|
|
115
|
+
interval_non_empty(interval(Lo, _, Hi, _, _)) :- Lo < Hi, !.
|
|
116
|
+
interval_non_empty(interval(Lo, closed, Hi, closed, Excluded)) :-
|
|
117
|
+
Lo =:= Hi,
|
|
118
|
+
\+ ( member(Value, Excluded), Value =:= Lo ).
|
|
119
|
+
|
|
120
|
+
% Integer variables: every strict or fractional bound is tightened to the
|
|
121
|
+
% nearest admissible integer, so the set is an integer range [Lo, Hi] minus
|
|
122
|
+
% finitely many excluded integers. An eq bound with a fractional value admits
|
|
123
|
+
% no integer at all.
|
|
124
|
+
integer_variable_feasible(Variable, Constraints) :-
|
|
125
|
+
findall(Op-Value, (member(c(Op, Other, Value), Constraints), Other == Variable), Bounds),
|
|
126
|
+
foldl(apply_integer_bound, Bounds, ibox(ninf, pinf, []), Box),
|
|
127
|
+
integer_box_non_empty(Box).
|
|
128
|
+
|
|
129
|
+
apply_integer_bound(_, empty, empty) :- !.
|
|
130
|
+
apply_integer_bound(eq-Value, ibox(Lo, Hi, Ex), Box) :- !,
|
|
131
|
+
( integral_value(Value, Integer)
|
|
132
|
+
-> integer_raise_low(Lo, Integer, Lo1),
|
|
133
|
+
integer_lower_high(Hi, Integer, Hi1),
|
|
134
|
+
Box = ibox(Lo1, Hi1, Ex)
|
|
135
|
+
; Box = empty
|
|
136
|
+
).
|
|
137
|
+
apply_integer_bound(neq-Value, ibox(Lo, Hi, Ex), ibox(Lo, Hi, Ex1)) :- !,
|
|
138
|
+
( integral_value(Value, Integer)
|
|
139
|
+
-> Ex1 = [Integer|Ex]
|
|
140
|
+
; Ex1 = Ex
|
|
141
|
+
).
|
|
142
|
+
apply_integer_bound(gt-Value, ibox(Lo, Hi, Ex), ibox(Lo1, Hi, Ex)) :- !,
|
|
143
|
+
Bound is floor(Value) + 1,
|
|
144
|
+
integer_raise_low(Lo, Bound, Lo1).
|
|
145
|
+
apply_integer_bound(gte-Value, ibox(Lo, Hi, Ex), ibox(Lo1, Hi, Ex)) :- !,
|
|
146
|
+
Bound is ceiling(Value),
|
|
147
|
+
integer_raise_low(Lo, Bound, Lo1).
|
|
148
|
+
apply_integer_bound(lt-Value, ibox(Lo, Hi, Ex), ibox(Lo, Hi1, Ex)) :- !,
|
|
149
|
+
Bound is ceiling(Value) - 1,
|
|
150
|
+
integer_lower_high(Hi, Bound, Hi1).
|
|
151
|
+
apply_integer_bound(lte-Value, ibox(Lo, Hi, Ex), ibox(Lo, Hi1, Ex)) :- !,
|
|
152
|
+
Bound is floor(Value),
|
|
153
|
+
integer_lower_high(Hi, Bound, Hi1).
|
|
154
|
+
|
|
155
|
+
integral_value(Value, Integer) :-
|
|
156
|
+
number(Value),
|
|
157
|
+
Integer is round(Value),
|
|
158
|
+
Integer =:= Value.
|
|
159
|
+
|
|
160
|
+
integer_raise_low(ninf, Bound, Bound) :- !.
|
|
161
|
+
integer_raise_low(Lo, Bound, Lo1) :- Lo1 is max(Lo, Bound).
|
|
162
|
+
|
|
163
|
+
integer_lower_high(pinf, Bound, Bound) :- !.
|
|
164
|
+
integer_lower_high(Hi, Bound, Hi1) :- Hi1 is min(Hi, Bound).
|
|
165
|
+
|
|
166
|
+
% An unbounded integer range cannot be emptied by finitely many exclusions.
|
|
167
|
+
integer_box_non_empty(empty) :- !, fail.
|
|
168
|
+
integer_box_non_empty(ibox(ninf, _, _)) :- !.
|
|
169
|
+
integer_box_non_empty(ibox(_, pinf, _)) :- !.
|
|
170
|
+
integer_box_non_empty(ibox(Lo, Hi, Excluded)) :-
|
|
171
|
+
Lo =< Hi,
|
|
172
|
+
findall(Value, (member(Value, Excluded), Value >= Lo, Value =< Hi), Inside0),
|
|
173
|
+
sort(Inside0, Inside),
|
|
174
|
+
length(Inside, ExcludedCount),
|
|
175
|
+
Hi - Lo + 1 > ExcludedCount.
|
package/src/kb.pl
CHANGED
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
deprecated_no_successor/1,
|
|
60
60
|
symbol_no_req_coverage/2,
|
|
61
61
|
predicate_schema/6,
|
|
62
|
+
predicate_schema_keys/4,
|
|
62
63
|
predicate_fact/5,
|
|
63
64
|
canonical_property_tuple/9,
|
|
64
65
|
contradicting_reqs/3,
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
:- use_module('../schema/entities.pl', [entity_type/1, entity_property/3, required_property/2]).
|
|
86
87
|
:- use_module('../schema/relationships.pl', [relationship_type/1, valid_relationship/3]).
|
|
87
88
|
:- use_module('units.pl', [canonical_quantity/6]).
|
|
89
|
+
:- use_module('intervals.pl', [numeric_constraints_satisfiable/2]).
|
|
88
90
|
:- use_module('../schema/validation.pl', [validate_entity/2, validate_relationship/3]).
|
|
89
91
|
|
|
90
92
|
% Constants
|
|
@@ -2569,8 +2571,8 @@ req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
|
2569
2571
|
intervals_overlap(ValidFromA, ValidToA, ValidFromB, ValidToB),
|
|
2570
2572
|
% Compare canonical quantities (30 min == 1800 s); witnesses keep the
|
|
2571
2573
|
% authored values so evidence still points at the source text.
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
+
comparison_quantity(ValTypeA, ValA, UnitA, CanonTypeA, CanonValA, CanonUnitA),
|
|
2575
|
+
comparison_quantity(ValTypeB, ValB, UnitB, CanonTypeB, CanonValB, CanonUnitB),
|
|
2574
2576
|
( polarity_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, ScopeA, PolarityA,
|
|
2575
2577
|
OpB, CanonTypeB, CanonValB, CanonUnitB, ScopeB, PolarityB, Reason)
|
|
2576
2578
|
; property_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, PolarityA,
|
|
@@ -2728,6 +2730,21 @@ canonical_property_tuple(FactId, Subject, Property, Op, CanonType, CanonValue, C
|
|
|
2728
2730
|
fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope, Polarity),
|
|
2729
2731
|
canonical_quantity(ValType, Value, Unit, CanonType, CanonValue, CanonUnit).
|
|
2730
2732
|
|
|
2733
|
+
%% comparison_quantity(+ValType, +Value, +Unit, -Type, -CanonValue, -CanonUnit)
|
|
2734
|
+
% implements REQ-kibi-truthful-consistency
|
|
2735
|
+
% canonical_quantity/6 for comparisons that depend on the value domain.
|
|
2736
|
+
% Canonicalization reports any integral value as int, but a value declared
|
|
2737
|
+
% `number` keeps real semantics: number gt 0 and number lt 1 do not conflict
|
|
2738
|
+
% even though 0 and 1 are integral. An int that scales to a fraction
|
|
2739
|
+
% (int 1 ms is 0.001 s) is compared as a number, which only admits more
|
|
2740
|
+
% values.
|
|
2741
|
+
comparison_quantity(ValType, Value, Unit, Type, CanonValue, CanonUnit) :-
|
|
2742
|
+
canonical_quantity(ValType, Value, Unit, CanonType, CanonValue, CanonUnit),
|
|
2743
|
+
( ValType == number, CanonType == int
|
|
2744
|
+
-> Type = number
|
|
2745
|
+
; Type = CanonType
|
|
2746
|
+
).
|
|
2747
|
+
|
|
2731
2748
|
%% predicate_schema(+FactId, -Namespace, -Name, -Arity, -ArgumentNames, -ArgumentTypes)
|
|
2732
2749
|
% Read one project-local ontology predicate schema fact.
|
|
2733
2750
|
predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :-
|
|
@@ -2744,6 +2761,27 @@ predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :
|
|
|
2744
2761
|
memberchk(argument_types=ArgumentTypesRaw, Props),
|
|
2745
2762
|
normalize_term_atom_list(ArgumentTypesRaw, ArgumentTypes).
|
|
2746
2763
|
|
|
2764
|
+
%% predicate_schema_keys(?Namespace, ?Name, ?Arity, -KeyPositions)
|
|
2765
|
+
% implements REQ-kibi-truthful-consistency
|
|
2766
|
+
% A predicate_schema that declares key_arguments states a functional
|
|
2767
|
+
% dependency: the remaining arguments are determined by the key arguments.
|
|
2768
|
+
% KeyPositions are the 1-based positions of the named key arguments. A
|
|
2769
|
+
% predicate without this declaration is treated as multivalued, so two atoms
|
|
2770
|
+
% of it are never assumed to describe the same fact.
|
|
2771
|
+
predicate_schema_keys(Namespace, Name, Arity, KeyPositions) :-
|
|
2772
|
+
predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, _ArgumentTypes),
|
|
2773
|
+
kb_entity(FactId, fact, Props),
|
|
2774
|
+
memberchk(key_arguments=KeysRaw, Props),
|
|
2775
|
+
normalize_term_atom_list(KeysRaw, KeyNames),
|
|
2776
|
+
KeyNames \= [],
|
|
2777
|
+
length(ArgumentNames, Arity),
|
|
2778
|
+
maplist(argument_name_position(ArgumentNames), KeyNames, Positions0),
|
|
2779
|
+
sort(Positions0, KeyPositions).
|
|
2780
|
+
|
|
2781
|
+
argument_name_position(ArgumentNames, KeyName, Position) :-
|
|
2782
|
+
nth1(Position, ArgumentNames, KeyName),
|
|
2783
|
+
!.
|
|
2784
|
+
|
|
2747
2785
|
%% predicate_fact(+FactId, -Namespace, -Name, -Args, -Polarity)
|
|
2748
2786
|
% Read one ground ontology predicate fact.
|
|
2749
2787
|
predicate_fact(FactId, Namespace, Name, Args, Polarity) :-
|
|
@@ -2842,9 +2880,23 @@ property_conflict(Subject, Property, OpA, TypeA, ValA, UnitA, Polarity,
|
|
|
2842
2880
|
OpB, TypeB, ValB, UnitB, Polarity, Reason) :-
|
|
2843
2881
|
unit_compatible(UnitA, UnitB),
|
|
2844
2882
|
compatible_types(TypeA, TypeB),
|
|
2845
|
-
|
|
2883
|
+
conflict_value_type(TypeA, TypeB, Type),
|
|
2884
|
+
values_conflict(OpA, ValA, OpB, ValB, Type),
|
|
2846
2885
|
format(atom(Reason), 'Value conflict on ~w.~w: ~w ~w vs ~w ~w', [Subject, Property, OpA, ValA, OpB, ValB]).
|
|
2847
2886
|
|
|
2887
|
+
%% conflict_value_type(+TypeA, +TypeB, -Type)
|
|
2888
|
+
% The value domain both constraints are compared in. A property is treated as
|
|
2889
|
+
% integer-valued only when both sides declare int; a mixed int/number pair is
|
|
2890
|
+
% compared over the reals so a conflict is never claimed from one side's
|
|
2891
|
+
% narrower declaration.
|
|
2892
|
+
% implements REQ-kibi-truthful-consistency
|
|
2893
|
+
conflict_value_type(int, int, int) :- !.
|
|
2894
|
+
conflict_value_type(TypeA, TypeB, number) :-
|
|
2895
|
+
is_numeric_type(TypeA),
|
|
2896
|
+
is_numeric_type(TypeB),
|
|
2897
|
+
!.
|
|
2898
|
+
conflict_value_type(Type, _, Type).
|
|
2899
|
+
|
|
2848
2900
|
%% compatible_types(+TypeA, +TypeB)
|
|
2849
2901
|
% Types are compatible if they are the same or both numeric.
|
|
2850
2902
|
compatible_types(T, T) :- !.
|
|
@@ -2898,33 +2950,28 @@ values_conflict(eq, ValA, neq, ValB, Type) :-
|
|
|
2898
2950
|
values_conflict(neq, ValA, eq, ValB, Type) :-
|
|
2899
2951
|
same_value(Type, ValA, Type, ValB).
|
|
2900
2952
|
|
|
2901
|
-
% Numeric
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
% Also catch lt/gt variants
|
|
2910
|
-
values_conflict(lt, ValA, gt, ValB, Type) :-
|
|
2911
|
-
is_numeric_type(Type),
|
|
2912
|
-
ValA =< ValB.
|
|
2913
|
-
values_conflict(gt, ValB, lt, ValA, Type) :-
|
|
2914
|
-
is_numeric_type(Type),
|
|
2915
|
-
ValA =< ValB.
|
|
2916
|
-
values_conflict(lt, ValA, gte, ValB, Type) :-
|
|
2917
|
-
is_numeric_type(Type),
|
|
2918
|
-
ValA =< ValB.
|
|
2919
|
-
values_conflict(gte, ValB, lt, ValA, Type) :-
|
|
2920
|
-
is_numeric_type(Type),
|
|
2921
|
-
ValA =< ValB.
|
|
2922
|
-
values_conflict(lte, ValA, gt, ValB, Type) :-
|
|
2923
|
-
is_numeric_type(Type),
|
|
2924
|
-
ValA < ValB.
|
|
2925
|
-
values_conflict(gt, ValB, lte, ValA, Type) :-
|
|
2953
|
+
% Numeric conflict: the two comparisons admit no common value of the type's
|
|
2954
|
+
% domain. `number` ranges over the reals; `int` ranges over the integers, so
|
|
2955
|
+
% gt 0 and lt 1 conflict for int (no integer lies strictly between them) but
|
|
2956
|
+
% not for number. This covers every operator pair (eq/gt, lte/gt at the same
|
|
2957
|
+
% bound, gte/gt, ...) instead of a hand-written table. neq/neq never
|
|
2958
|
+
% conflicts and eq/neq is handled above.
|
|
2959
|
+
% implements REQ-kibi-truthful-consistency
|
|
2960
|
+
values_conflict(OpA, ValA, OpB, ValB, Type) :-
|
|
2926
2961
|
is_numeric_type(Type),
|
|
2927
|
-
|
|
2962
|
+
numeric_operator(OpA),
|
|
2963
|
+
numeric_operator(OpB),
|
|
2964
|
+
\+ ( OpA == eq, OpB == eq ),
|
|
2965
|
+
\+ ( OpA == eq, OpB == neq ),
|
|
2966
|
+
\+ ( OpA == neq, OpB == eq ),
|
|
2967
|
+
number(ValA), number(ValB),
|
|
2968
|
+
numeric_type_integer_variables(Type, IntegerVariables),
|
|
2969
|
+
\+ numeric_constraints_satisfiable([c(OpA, X, ValA), c(OpB, X, ValB)], IntegerVariables).
|
|
2970
|
+
|
|
2971
|
+
numeric_type_integer_variables(int, all) :- !.
|
|
2972
|
+
numeric_type_integer_variables(_, []).
|
|
2973
|
+
|
|
2974
|
+
numeric_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]).
|
|
2928
2975
|
|
|
2929
2976
|
%% is_numeric_type(+Type)
|
|
2930
2977
|
% True for numeric value types.
|