kibi-core 0.14.1 → 0.15.1
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/README.md +21 -0
- package/package.json +7 -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 +87 -31
- 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
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
kb_query_entities/8,
|
|
27
27
|
kb_search_entities/6,
|
|
28
28
|
kb_list_search_candidates/5,
|
|
29
|
+
kb_list_search_candidates/6,
|
|
29
30
|
kb_entity_ids/1,
|
|
30
31
|
kb_rebuild_indexes/0,
|
|
31
32
|
kb_entity/3,
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
deprecated_no_successor/1,
|
|
60
61
|
symbol_no_req_coverage/2,
|
|
61
62
|
predicate_schema/6,
|
|
63
|
+
predicate_schema_keys/4,
|
|
62
64
|
predicate_fact/5,
|
|
63
65
|
canonical_property_tuple/9,
|
|
64
66
|
contradicting_reqs/3,
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
:- use_module('../schema/entities.pl', [entity_type/1, entity_property/3, required_property/2]).
|
|
86
88
|
:- use_module('../schema/relationships.pl', [relationship_type/1, valid_relationship/3]).
|
|
87
89
|
:- use_module('units.pl', [canonical_quantity/6]).
|
|
90
|
+
:- use_module('intervals.pl', [numeric_constraints_satisfiable/2]).
|
|
88
91
|
:- use_module('../schema/validation.pl', [validate_entity/2, validate_relationship/3]).
|
|
89
92
|
|
|
90
93
|
% Constants
|
|
@@ -1814,14 +1817,22 @@ kb_search_entities(TypeFilter, Query, Limit, Offset, Rows, Count) :-
|
|
|
1814
1817
|
% candidate rows, ordered by id. Semantic discovery uses it when no lexical
|
|
1815
1818
|
% token can pre-filter the candidate set.
|
|
1816
1819
|
kb_list_search_candidates(TypeFilter, Limit, Offset, Rows, Count) :-
|
|
1820
|
+
kb_list_search_candidates(TypeFilter, none, Limit, Offset, Rows, Count).
|
|
1821
|
+
|
|
1822
|
+
%% kb_list_search_candidates(+Type, +Source, +Limit, +Offset, -Rows, -Count)
|
|
1823
|
+
% As kb_list_search_candidates/5, optionally restricted to entities whose
|
|
1824
|
+
% indexed source contains Source (the kb_query_entities/8 source filter).
|
|
1825
|
+
% Source-located search uses it so candidates stay projected rows instead of
|
|
1826
|
+
% complete entities.
|
|
1827
|
+
% implements REQ-mcp-search-discovery, REQ-kibi-intent-aware-source-discovery
|
|
1828
|
+
kb_list_search_candidates(TypeFilter, SourceFilter, Limit, Offset, Rows, Count) :-
|
|
1817
1829
|
integer(Limit),
|
|
1818
1830
|
integer(Offset),
|
|
1819
1831
|
Limit >= 0,
|
|
1820
1832
|
Offset >= 0,
|
|
1821
1833
|
kb_ensure_indexes,
|
|
1822
1834
|
findall(Id,
|
|
1823
|
-
(
|
|
1824
|
-
(TypeFilter == none -> true ; kb_index_type(TypeFilter, Id)) ),
|
|
1835
|
+
indexed_entity_match(TypeFilter, none, [], SourceFilter, Id),
|
|
1825
1836
|
RawIds),
|
|
1826
1837
|
sort(RawIds, Ids),
|
|
1827
1838
|
length(Ids, Count),
|
|
@@ -2569,8 +2580,8 @@ req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
|
2569
2580
|
intervals_overlap(ValidFromA, ValidToA, ValidFromB, ValidToB),
|
|
2570
2581
|
% Compare canonical quantities (30 min == 1800 s); witnesses keep the
|
|
2571
2582
|
% authored values so evidence still points at the source text.
|
|
2572
|
-
|
|
2573
|
-
|
|
2583
|
+
comparison_quantity(ValTypeA, ValA, UnitA, CanonTypeA, CanonValA, CanonUnitA),
|
|
2584
|
+
comparison_quantity(ValTypeB, ValB, UnitB, CanonTypeB, CanonValB, CanonUnitB),
|
|
2574
2585
|
( polarity_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, ScopeA, PolarityA,
|
|
2575
2586
|
OpB, CanonTypeB, CanonValB, CanonUnitB, ScopeB, PolarityB, Reason)
|
|
2576
2587
|
; property_conflict(SubjectKey, PropertyKey, OpA, CanonTypeA, CanonValA, CanonUnitA, PolarityA,
|
|
@@ -2728,6 +2739,21 @@ canonical_property_tuple(FactId, Subject, Property, Op, CanonType, CanonValue, C
|
|
|
2728
2739
|
fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope, Polarity),
|
|
2729
2740
|
canonical_quantity(ValType, Value, Unit, CanonType, CanonValue, CanonUnit).
|
|
2730
2741
|
|
|
2742
|
+
%% comparison_quantity(+ValType, +Value, +Unit, -Type, -CanonValue, -CanonUnit)
|
|
2743
|
+
% implements REQ-kibi-truthful-consistency
|
|
2744
|
+
% canonical_quantity/6 for comparisons that depend on the value domain.
|
|
2745
|
+
% Canonicalization reports any integral value as int, but a value declared
|
|
2746
|
+
% `number` keeps real semantics: number gt 0 and number lt 1 do not conflict
|
|
2747
|
+
% even though 0 and 1 are integral. An int that scales to a fraction
|
|
2748
|
+
% (int 1 ms is 0.001 s) is compared as a number, which only admits more
|
|
2749
|
+
% values.
|
|
2750
|
+
comparison_quantity(ValType, Value, Unit, Type, CanonValue, CanonUnit) :-
|
|
2751
|
+
canonical_quantity(ValType, Value, Unit, CanonType, CanonValue, CanonUnit),
|
|
2752
|
+
( ValType == number, CanonType == int
|
|
2753
|
+
-> Type = number
|
|
2754
|
+
; Type = CanonType
|
|
2755
|
+
).
|
|
2756
|
+
|
|
2731
2757
|
%% predicate_schema(+FactId, -Namespace, -Name, -Arity, -ArgumentNames, -ArgumentTypes)
|
|
2732
2758
|
% Read one project-local ontology predicate schema fact.
|
|
2733
2759
|
predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :-
|
|
@@ -2744,6 +2770,27 @@ predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :
|
|
|
2744
2770
|
memberchk(argument_types=ArgumentTypesRaw, Props),
|
|
2745
2771
|
normalize_term_atom_list(ArgumentTypesRaw, ArgumentTypes).
|
|
2746
2772
|
|
|
2773
|
+
%% predicate_schema_keys(?Namespace, ?Name, ?Arity, -KeyPositions)
|
|
2774
|
+
% implements REQ-kibi-truthful-consistency
|
|
2775
|
+
% A predicate_schema that declares key_arguments states a functional
|
|
2776
|
+
% dependency: the remaining arguments are determined by the key arguments.
|
|
2777
|
+
% KeyPositions are the 1-based positions of the named key arguments. A
|
|
2778
|
+
% predicate without this declaration is treated as multivalued, so two atoms
|
|
2779
|
+
% of it are never assumed to describe the same fact.
|
|
2780
|
+
predicate_schema_keys(Namespace, Name, Arity, KeyPositions) :-
|
|
2781
|
+
predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, _ArgumentTypes),
|
|
2782
|
+
kb_entity(FactId, fact, Props),
|
|
2783
|
+
memberchk(key_arguments=KeysRaw, Props),
|
|
2784
|
+
normalize_term_atom_list(KeysRaw, KeyNames),
|
|
2785
|
+
KeyNames \= [],
|
|
2786
|
+
length(ArgumentNames, Arity),
|
|
2787
|
+
maplist(argument_name_position(ArgumentNames), KeyNames, Positions0),
|
|
2788
|
+
sort(Positions0, KeyPositions).
|
|
2789
|
+
|
|
2790
|
+
argument_name_position(ArgumentNames, KeyName, Position) :-
|
|
2791
|
+
nth1(Position, ArgumentNames, KeyName),
|
|
2792
|
+
!.
|
|
2793
|
+
|
|
2747
2794
|
%% predicate_fact(+FactId, -Namespace, -Name, -Args, -Polarity)
|
|
2748
2795
|
% Read one ground ontology predicate fact.
|
|
2749
2796
|
predicate_fact(FactId, Namespace, Name, Args, Polarity) :-
|
|
@@ -2842,9 +2889,23 @@ property_conflict(Subject, Property, OpA, TypeA, ValA, UnitA, Polarity,
|
|
|
2842
2889
|
OpB, TypeB, ValB, UnitB, Polarity, Reason) :-
|
|
2843
2890
|
unit_compatible(UnitA, UnitB),
|
|
2844
2891
|
compatible_types(TypeA, TypeB),
|
|
2845
|
-
|
|
2892
|
+
conflict_value_type(TypeA, TypeB, Type),
|
|
2893
|
+
values_conflict(OpA, ValA, OpB, ValB, Type),
|
|
2846
2894
|
format(atom(Reason), 'Value conflict on ~w.~w: ~w ~w vs ~w ~w', [Subject, Property, OpA, ValA, OpB, ValB]).
|
|
2847
2895
|
|
|
2896
|
+
%% conflict_value_type(+TypeA, +TypeB, -Type)
|
|
2897
|
+
% The value domain both constraints are compared in. A property is treated as
|
|
2898
|
+
% integer-valued only when both sides declare int; a mixed int/number pair is
|
|
2899
|
+
% compared over the reals so a conflict is never claimed from one side's
|
|
2900
|
+
% narrower declaration.
|
|
2901
|
+
% implements REQ-kibi-truthful-consistency
|
|
2902
|
+
conflict_value_type(int, int, int) :- !.
|
|
2903
|
+
conflict_value_type(TypeA, TypeB, number) :-
|
|
2904
|
+
is_numeric_type(TypeA),
|
|
2905
|
+
is_numeric_type(TypeB),
|
|
2906
|
+
!.
|
|
2907
|
+
conflict_value_type(Type, _, Type).
|
|
2908
|
+
|
|
2848
2909
|
%% compatible_types(+TypeA, +TypeB)
|
|
2849
2910
|
% Types are compatible if they are the same or both numeric.
|
|
2850
2911
|
compatible_types(T, T) :- !.
|
|
@@ -2898,33 +2959,28 @@ values_conflict(eq, ValA, neq, ValB, Type) :-
|
|
|
2898
2959
|
values_conflict(neq, ValA, eq, ValB, Type) :-
|
|
2899
2960
|
same_value(Type, ValA, Type, ValB).
|
|
2900
2961
|
|
|
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) :-
|
|
2962
|
+
% Numeric conflict: the two comparisons admit no common value of the type's
|
|
2963
|
+
% domain. `number` ranges over the reals; `int` ranges over the integers, so
|
|
2964
|
+
% gt 0 and lt 1 conflict for int (no integer lies strictly between them) but
|
|
2965
|
+
% not for number. This covers every operator pair (eq/gt, lte/gt at the same
|
|
2966
|
+
% bound, gte/gt, ...) instead of a hand-written table. neq/neq never
|
|
2967
|
+
% conflicts and eq/neq is handled above.
|
|
2968
|
+
% implements REQ-kibi-truthful-consistency
|
|
2969
|
+
values_conflict(OpA, ValA, OpB, ValB, Type) :-
|
|
2926
2970
|
is_numeric_type(Type),
|
|
2927
|
-
|
|
2971
|
+
numeric_operator(OpA),
|
|
2972
|
+
numeric_operator(OpB),
|
|
2973
|
+
\+ ( OpA == eq, OpB == eq ),
|
|
2974
|
+
\+ ( OpA == eq, OpB == neq ),
|
|
2975
|
+
\+ ( OpA == neq, OpB == eq ),
|
|
2976
|
+
number(ValA), number(ValB),
|
|
2977
|
+
numeric_type_integer_variables(Type, IntegerVariables),
|
|
2978
|
+
\+ numeric_constraints_satisfiable([c(OpA, X, ValA), c(OpB, X, ValB)], IntegerVariables).
|
|
2979
|
+
|
|
2980
|
+
numeric_type_integer_variables(int, all) :- !.
|
|
2981
|
+
numeric_type_integer_variables(_, []).
|
|
2982
|
+
|
|
2983
|
+
numeric_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]).
|
|
2928
2984
|
|
|
2929
2985
|
%% is_numeric_type(+Type)
|
|
2930
2986
|
% True for numeric value types.
|