rdf-reasoner-konclude 0.2.0 → 0.3.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/README.md +430 -17
- package/dist/index.d.ts +180 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1062 -14
- package/dist/index.js.map +1 -1
- package/dist/intern.d.ts +8 -0
- package/dist/intern.d.ts.map +1 -1
- package/dist/intern.js +50 -0
- package/dist/intern.js.map +1 -1
- package/dist/konclude.mjs +1 -2
- package/dist/konclude.wasm +0 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +23 -2
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,26 +114,107 @@ The browser build requires COOP/COEP HTTP headers for `SharedArrayBuffer` (used
|
|
|
114
114
|
const reasoner = new RdfReasoner();
|
|
115
115
|
await reasoner.ready; // resolves when WASM module is loaded
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
await reasoner.classify(store);
|
|
119
|
-
|
|
117
|
+
// TBox: class hierarchy (rdfs:subClassOf, owl:equivalentClass)
|
|
118
|
+
await reasoner.classify(store);
|
|
119
|
+
|
|
120
|
+
// ABox: individual type entailments (rdf:type)
|
|
121
|
+
await reasoner.materialize(store);
|
|
122
|
+
await reasoner.materialize(store, { includeClassHierarchy: true }); // TBox + ABox
|
|
123
|
+
|
|
124
|
+
// Property hierarchy (rdfs:subPropertyOf)
|
|
125
|
+
await reasoner.classifyProperties(store);
|
|
126
|
+
|
|
127
|
+
// Consistency
|
|
128
|
+
const ok = await reasoner.checkConsistency(store); // true = consistent
|
|
129
|
+
|
|
130
|
+
// Entailment queries (post-reasoning)
|
|
131
|
+
const entailed = await reasoner.isEntailed(store, quad);
|
|
132
|
+
const results = await reasoner.isEntailed(store, [quad1, quad2]);
|
|
133
|
+
|
|
134
|
+
// Hypothetical reasoning
|
|
135
|
+
const { added, removed } = await reasoner.whatIf(store, [newAxiom]);
|
|
136
|
+
|
|
137
|
+
// Explanation / justification
|
|
138
|
+
const justs = await reasoner.explain(store, quad, { maxJustifications: 3 });
|
|
139
|
+
const inconsJusts = await reasoner.explainInconsistency(store);
|
|
140
|
+
|
|
141
|
+
// Satisfiability
|
|
142
|
+
const classes = await reasoner.getUnsatisfiableClasses(store);
|
|
143
|
+
const ok2 = await reasoner.isSatisfiable(store, "http://example.org/MyClass");
|
|
144
|
+
|
|
145
|
+
// Combined diagnostic
|
|
146
|
+
const report = await reasoner.validate(store);
|
|
147
|
+
// report.consistent, report.errors (Quad[][]), report.warnings (ClassWarning[])
|
|
120
148
|
|
|
121
149
|
reasoner.terminate(); // shut down the Worker
|
|
122
150
|
```
|
|
123
151
|
|
|
124
|
-
`
|
|
125
|
-
`INFERRED_GRAPH_IRI` named graph inside the store.
|
|
126
|
-
each call — do not store ontology triples there.
|
|
152
|
+
`classify(store)`, `materialize(store)`, and `classifyProperties(store)` write
|
|
153
|
+
inferred triples into the `INFERRED_GRAPH_IRI` named graph inside the store.
|
|
154
|
+
The graph is cleared before each call — do not store ontology triples there.
|
|
127
155
|
|
|
128
156
|
Named graphs in the input are dropped at the WASM boundary (NTriples wire
|
|
129
157
|
format is triple-only). Reasoning runs over the union of all graphs.
|
|
130
158
|
|
|
131
|
-
Options for `
|
|
159
|
+
Options for `classify(store, opts)`, `materialize(store, opts)`, and `classifyProperties(store, opts)`:
|
|
132
160
|
|
|
133
161
|
```typescript
|
|
162
|
+
// classify, classifyProperties
|
|
134
163
|
interface StoreReasoningOptions {
|
|
135
|
-
inferredGraph?: string; //
|
|
136
|
-
|
|
164
|
+
inferredGraph?: string; // default: INFERRED_GRAPH_IRI
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// materialize
|
|
168
|
+
interface MaterializeStoreOptions {
|
|
169
|
+
inferredGraph?: string; // default: INFERRED_GRAPH_IRI
|
|
170
|
+
includeClassHierarchy?: boolean; // also emit subClassOf/equivalentClass; default false
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Options for the Phase 2 axiom-work methods:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// isEntailed: no options
|
|
178
|
+
|
|
179
|
+
// whatIf
|
|
180
|
+
interface WhatIfOptions {
|
|
181
|
+
removals?: Quad[]; // quads to remove from the base ontology
|
|
182
|
+
outputGraph?: string; // named graph for hypothetical inferences
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// explain / explainInconsistency
|
|
186
|
+
interface ExplainOptions {
|
|
187
|
+
maxJustifications?: number; // default: 1
|
|
188
|
+
axiomFilter?: (q: Quad) => boolean; // restrict candidate axiom set
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// validate
|
|
192
|
+
interface ValidateOptions {
|
|
193
|
+
maxJustificationsPerError?: number; // default: 1
|
|
194
|
+
maxJustificationsPerWarning?: number; // default: 1 (pass 0 for IRI-only scan)
|
|
195
|
+
axiomFilter?: (q: Quad) => boolean;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Return types for the Phase 2 axiom-work methods:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Return types
|
|
203
|
+
isEntailed(store, axiom: Quad): Promise<boolean | null>
|
|
204
|
+
isEntailed(store, axioms: Quad[]): Promise<(boolean | null)[]>
|
|
205
|
+
whatIf(store, additions): Promise<{ added: Quad[], removed: Quad[] }>
|
|
206
|
+
explain(store, axiom): Promise<Quad[][]> // [] if not entailed; throws for unsupported predicate
|
|
207
|
+
explainInconsistency(store): Promise<Quad[][]> // [] if consistent
|
|
208
|
+
validate(store): Promise<ValidationResult>
|
|
209
|
+
// where:
|
|
210
|
+
interface ValidationResult {
|
|
211
|
+
consistent: boolean;
|
|
212
|
+
errors: Quad[][]; // MIPS; non-empty only when consistent === false
|
|
213
|
+
warnings: ClassWarning[]; // one per unsatisfiable class
|
|
214
|
+
}
|
|
215
|
+
interface ClassWarning {
|
|
216
|
+
classIRI: string;
|
|
217
|
+
justifications: Quad[][];
|
|
137
218
|
}
|
|
138
219
|
```
|
|
139
220
|
|
|
@@ -144,11 +225,315 @@ import { INFERRED_GRAPH_IRI } from "rdf-reasoner-konclude";
|
|
|
144
225
|
// "urn:konclude:inferred"
|
|
145
226
|
```
|
|
146
227
|
|
|
147
|
-
The default named graph where inferred triples are written
|
|
228
|
+
The default named graph where inferred triples are written.
|
|
148
229
|
|
|
149
230
|
### Deprecated overloads
|
|
150
231
|
|
|
151
|
-
`reason(quads
|
|
232
|
+
`reason(quads)`, `classify(quads)`, `checkConsistency(quads)`, and `materialize(quads)` accept a raw `Iterable<Quad>` and return `Promise<Quad[]>` / `Promise<boolean>`. These overloads are deprecated — use the `Store`-based API instead.
|
|
233
|
+
|
|
234
|
+
## Common workflows
|
|
235
|
+
|
|
236
|
+
All examples assume a single `RdfReasoner` instance:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { RdfReasoner, INFERRED_GRAPH_IRI } from "rdf-reasoner-konclude";
|
|
240
|
+
import { Store, Parser, DataFactory } from "n3";
|
|
241
|
+
|
|
242
|
+
const reasoner = new RdfReasoner();
|
|
243
|
+
await reasoner.ready;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### TBox only: classify a class hierarchy
|
|
247
|
+
|
|
248
|
+
Use `classify(store)` when the ontology has no named individuals. Returns
|
|
249
|
+
`rdfs:subClassOf` and `owl:equivalentClass` entailments.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
await reasoner.classify(store);
|
|
253
|
+
const subClasses = store.getQuads(
|
|
254
|
+
null,
|
|
255
|
+
"http://www.w3.org/2000/01/rdf-schema#subClassOf",
|
|
256
|
+
null,
|
|
257
|
+
INFERRED_GRAPH_IRI,
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### ABox: derive individual types
|
|
262
|
+
|
|
263
|
+
Use `materialize(store)` when the ontology includes named individuals. Returns
|
|
264
|
+
`rdf:type` entailments for every individual that can be classified under the
|
|
265
|
+
TBox. Classification runs internally as part of realization — no separate call
|
|
266
|
+
needed.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
await reasoner.materialize(store);
|
|
270
|
+
const types = store.getQuads(
|
|
271
|
+
null,
|
|
272
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
|
|
273
|
+
null,
|
|
274
|
+
INFERRED_GRAPH_IRI,
|
|
275
|
+
);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
To also receive the class hierarchy in the same call:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
await reasoner.materialize(store, { includeClassHierarchy: true });
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Property hierarchy
|
|
285
|
+
|
|
286
|
+
Use `classifyProperties(store)` to infer `rdfs:subPropertyOf` entailments.
|
|
287
|
+
Requires `owl:ObjectProperty` / `owl:DatatypeProperty` declarations in the
|
|
288
|
+
ontology.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
await reasoner.classifyProperties(store);
|
|
292
|
+
const subProps = store.getQuads(
|
|
293
|
+
null,
|
|
294
|
+
"http://www.w3.org/2000/01/rdf-schema#subPropertyOf",
|
|
295
|
+
null,
|
|
296
|
+
INFERRED_GRAPH_IRI,
|
|
297
|
+
);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Combine class hierarchy + property hierarchy
|
|
301
|
+
|
|
302
|
+
`INFERRED_GRAPH_IRI` is cleared by each call, so write results to separate
|
|
303
|
+
named graphs if you need both at once:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
await reasoner.classify(store, { inferredGraph: "urn:myapp:class-inferred" });
|
|
307
|
+
await reasoner.classifyProperties(store, {
|
|
308
|
+
inferredGraph: "urn:myapp:prop-inferred",
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Or if storing them together is fine, call in sequence and read after both:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
await reasoner.classify(store);
|
|
316
|
+
const classTriples = store.getQuads(null, null, null, INFERRED_GRAPH_IRI);
|
|
317
|
+
|
|
318
|
+
await reasoner.classifyProperties(store); // clears + repopulates INFERRED_GRAPH_IRI
|
|
319
|
+
const propTriples = store.getQuads(null, null, null, INFERRED_GRAPH_IRI);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Consistency checking
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const consistent = await reasoner.checkConsistency(store);
|
|
326
|
+
if (!consistent) {
|
|
327
|
+
console.error("Ontology is inconsistent");
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Incremental ontology evolution
|
|
332
|
+
|
|
333
|
+
The reasoner resets and re-loads the full store on every call. To reason over
|
|
334
|
+
an evolving ontology, mutate the store and call again:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
await reasoner.classify(store);
|
|
338
|
+
// → first inference written to INFERRED_GRAPH_IRI
|
|
339
|
+
|
|
340
|
+
// Add new axioms
|
|
341
|
+
store.addQuad(
|
|
342
|
+
DataFactory.quad(
|
|
343
|
+
DataFactory.namedNode("http://example.org/Cat"),
|
|
344
|
+
DataFactory.namedNode("http://www.w3.org/2000/01/rdf-schema#subClassOf"),
|
|
345
|
+
DataFactory.namedNode("http://example.org/Animal"),
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// Re-reason: INFERRED_GRAPH_IRI is cleared and fully repopulated
|
|
350
|
+
await reasoner.classify(store);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Concurrent calls on the same `RdfReasoner` instance are serialized
|
|
354
|
+
automatically — queued calls run in order, never interleaved.
|
|
355
|
+
|
|
356
|
+
### Checking if a specific entailment holds (`isEntailed`)
|
|
357
|
+
|
|
358
|
+
`isEntailed` supports `rdfs:subClassOf`, `owl:equivalentClass`, `rdf:type`, and
|
|
359
|
+
`rdfs:subPropertyOf`. Returns `null` for unsupported predicates.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
await reasoner.materialize(store);
|
|
363
|
+
const isAnimal = await reasoner.isEntailed(
|
|
364
|
+
store,
|
|
365
|
+
DataFactory.quad(
|
|
366
|
+
DataFactory.namedNode("http://example.org/Fido"),
|
|
367
|
+
DataFactory.namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
|
|
368
|
+
DataFactory.namedNode("http://example.org/Animal"),
|
|
369
|
+
),
|
|
370
|
+
);
|
|
371
|
+
// true if Fido rdf:type Animal is entailed
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Hypothetical reasoning (`whatIf`)
|
|
375
|
+
|
|
376
|
+
`whatIf` reasons over a temporary store mutation without modifying the input store.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const { added, removed } = await reasoner.whatIf(store, [newAxiom]);
|
|
380
|
+
// added/removed are deltas relative to the current INFERRED_GRAPH_IRI
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Explaining an entailment (`explain`)
|
|
384
|
+
|
|
385
|
+
`explain` returns minimal justifications — each is the smallest set of axioms
|
|
386
|
+
that alone entails the target. Returns `[]` if the axiom is not entailed.
|
|
387
|
+
Each BlackBox call issues multiple Worker round-trips; use `maxJustifications: 1`
|
|
388
|
+
for cheap first-justification lookups.
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const justs = await reasoner.explain(store, quad, { maxJustifications: 3 });
|
|
392
|
+
// justs: Quad[][] — each inner array is one minimal justification
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Diagnosing an inconsistency (`explainInconsistency`)
|
|
396
|
+
|
|
397
|
+
Returns minimal inconsistent sub-ontologies (MIPS). Returns `[]` on consistent ontologies.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
const mips = await reasoner.explainInconsistency(store);
|
|
401
|
+
if (mips.length > 0) {
|
|
402
|
+
console.log("Minimal inconsistent core:", mips[0]);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Finding unsatisfiable classes (`getUnsatisfiableClasses`, `isSatisfiable`)
|
|
407
|
+
|
|
408
|
+
`owl:Nothing` is excluded from the output. Classes absent from the taxonomy
|
|
409
|
+
return `true` from `isSatisfiable` (open-world assumption).
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
const unsatClasses = await reasoner.getUnsatisfiableClasses(store);
|
|
413
|
+
// ["http://example.org/EmptyClass", ...]
|
|
414
|
+
|
|
415
|
+
const ok = await reasoner.isSatisfiable(store, "http://example.org/MyClass");
|
|
416
|
+
// false if MyClass is equivalent to owl:Nothing in the ontology
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Full ontology validation (`validate`)
|
|
420
|
+
|
|
421
|
+
`validate` combines consistency checking, unsatisfiable-class detection, and
|
|
422
|
+
optional justification computation in a single call.
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
const report = await reasoner.validate(store);
|
|
426
|
+
// report.consistent — true/false
|
|
427
|
+
// report.errors — Quad[][], minimal inconsistent sub-ontologies (non-empty when inconsistent)
|
|
428
|
+
// report.warnings — ClassWarning[], one per unsatisfiable class
|
|
429
|
+
|
|
430
|
+
// Cheap IRI-only scan (skip BlackBox justifications for warnings):
|
|
431
|
+
const quickReport = await reasoner.validate(store, {
|
|
432
|
+
maxJustificationsPerWarning: 0,
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
`errors` are non-empty only when `consistent` is `false`. When `consistent` is
|
|
437
|
+
`true`, only `warnings` may be present. Callers should check `consistent` before
|
|
438
|
+
acting on `warnings`.
|
|
439
|
+
|
|
440
|
+
### Separate inferred graph per operation
|
|
441
|
+
|
|
442
|
+
The `INFERRED_GRAPH_IRI` graph is cleared before each call. If you need to
|
|
443
|
+
preserve results from a previous call while re-reasoning with a different
|
|
444
|
+
operation, use separate named graphs:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
await reasoner.classify(store, { inferredGraph: "urn:myapp:class-hierarchy" });
|
|
448
|
+
await reasoner.materialize(store, { inferredGraph: "urn:myapp:abox-types" });
|
|
449
|
+
|
|
450
|
+
// Both named graphs now coexist in the store
|
|
451
|
+
const classTriples = store.getQuads(
|
|
452
|
+
null,
|
|
453
|
+
null,
|
|
454
|
+
null,
|
|
455
|
+
"urn:myapp:class-hierarchy",
|
|
456
|
+
);
|
|
457
|
+
const typeTriples = store.getQuads(null, null, null, "urn:myapp:abox-types");
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Node.js and browser: same API
|
|
461
|
+
|
|
462
|
+
`RdfReasoner` is identical in both environments. Under Node.js the `"node"`
|
|
463
|
+
export condition in `package.json` loads `index.node.mjs`, which installs a
|
|
464
|
+
`NodeWorkerShim` over `node:worker_threads` that matches the browser `Worker`
|
|
465
|
+
interface. The worker dispatch code (`dist/worker.js`) and all protocol messages
|
|
466
|
+
are shared. No code changes are needed when porting between environments.
|
|
467
|
+
|
|
468
|
+
## OWL 2 DL coverage
|
|
469
|
+
|
|
470
|
+
Konclude implements the full OWL 2 DL tableau (expressivity SROIQ(D)). The sections below
|
|
471
|
+
document what has been verified to work, what is known not to work, and what the WASM port
|
|
472
|
+
does not yet surface as output.
|
|
473
|
+
|
|
474
|
+
### Consistency checking (`checkConsistency`)
|
|
475
|
+
|
|
476
|
+
Violation detection verified against native Konclude v0.7.0 ground truth:
|
|
477
|
+
|
|
478
|
+
| # | Violation pattern | Native | WASM | Status |
|
|
479
|
+
| --- | --------------------------------------------------------- | ------------------ | -------------- | ---------------------------------- |
|
|
480
|
+
| 1 | `owl:disjointWith` (direct) | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
481
|
+
| 2 | `owl:disjointWith` (via domain/range inference) | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
482
|
+
| 3 | `owl:AsymmetricProperty` bidirectional assertion | consistent ✗ (bug) | inconsistent ✓ | **PARITY (WASM surpasses native)** |
|
|
483
|
+
| 4 | `owl:IrreflexiveProperty` self-reference | consistent ✗ (bug) | inconsistent ✓ | **PARITY (WASM surpasses native)** |
|
|
484
|
+
| 5 | `owl:maxQualifiedCardinality` + `owl:differentFrom` | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
485
|
+
| 6 | `owl:allValuesFrom` + `owl:disjointWith` | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
486
|
+
| 7 | `owl:ReflexiveProperty` + `ObjectComplementOf(HasSelf)` | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
487
|
+
| 8 | `owl:InverseFunctionalProperty` + `DifferentIndividuals` | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
488
|
+
| 9 | `owl:AllDisjointClasses` (3-way) + double membership | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
489
|
+
| 10 | `DisjointObjectProperties` + `EquivalentObjectProperties` | consistent ✗ (bug) | inconsistent ✓ | **PARITY (WASM surpasses native)** |
|
|
490
|
+
| 11 | `owl:disjointUnionOf` + double membership | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
491
|
+
| 12 | `owl:NegativeObjectPropertyAssertion` contradiction | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
492
|
+
| 13 | `DataAllValuesFrom xsd:minInclusive` (consistent case) | consistent ✓ | consistent ✓ | **PARITY** |
|
|
493
|
+
| 14 | `DataAllValuesFrom xsd:minInclusive` (inconsistent case) | inconsistent ✓ | inconsistent ✓ | **PARITY** |
|
|
494
|
+
|
|
495
|
+
**PARITY (WASM surpasses native v0.7.0)** means native Konclude v0.7.0 has a kernel bug for this
|
|
496
|
+
construct; this package fixes it via patches 011–012 (role axiom correctness, NPA builder fix).
|
|
497
|
+
|
|
498
|
+
`owl:AllDisjointClasses`, `owl:disjointUnionOf`, and `owl:NegativePropertyAssertion` all work in
|
|
499
|
+
`materialize()` — the JS layer expands list axioms to pairwise form before handing off to WASM.
|
|
500
|
+
The only known remaining gap is `owl:FunctionalProperty` + `owl:InverseFunctionalProperty` on the
|
|
501
|
+
same role with a single filler (ALIF+ precompute hang). This case is tracked in
|
|
502
|
+
`tests/integration/known-limitations.test.ts`.
|
|
503
|
+
|
|
504
|
+
### Classification (`classify`)
|
|
505
|
+
|
|
506
|
+
Verified working:
|
|
507
|
+
|
|
508
|
+
- `rdfs:subClassOf` — transitive closure, with `owl:equivalentClass` folding
|
|
509
|
+
- `owl:intersectionOf`, `owl:unionOf`, `owl:complementOf`
|
|
510
|
+
- `owl:someValuesFrom`, `owl:allValuesFrom`
|
|
511
|
+
- `owl:minCardinality`, `owl:maxCardinality`, `owl:exactCardinality`
|
|
512
|
+
- `owl:minQualifiedCardinality`, `owl:maxQualifiedCardinality`, `owl:qualifiedCardinality`
|
|
513
|
+
- `owl:ObjectProperty` with `rdfs:domain`, `rdfs:range`, `owl:inverseOf`
|
|
514
|
+
- `owl:TransitiveProperty`, `owl:FunctionalProperty`, `owl:InverseFunctionalProperty`
|
|
515
|
+
- `owl:ReflexiveProperty`, `owl:SymmetricProperty`
|
|
516
|
+
- `owl:propertyChainAxiom`
|
|
517
|
+
|
|
518
|
+
### ABox realization (`materialize`)
|
|
519
|
+
|
|
520
|
+
`materialize(store)` infers `rdf:type` entailments for named individuals under the
|
|
521
|
+
class hierarchy. Verified working on ontologies up to ~25 000 individuals (LUBM + data).
|
|
522
|
+
|
|
523
|
+
Input ABox axioms accepted:
|
|
524
|
+
|
|
525
|
+
- `rdf:type` assertions
|
|
526
|
+
- Object property assertions (`owl:ObjectProperty`)
|
|
527
|
+
- `owl:differentFrom` / `owl:AllDifferent`
|
|
528
|
+
|
|
529
|
+
### Known output gaps
|
|
530
|
+
|
|
531
|
+
| Feature | Status |
|
|
532
|
+
| ----------------------------------------- | ----------------------------------------- |
|
|
533
|
+
| `rdfs:subPropertyOf` (property hierarchy) | Available via `classifyProperties(store)` |
|
|
534
|
+
| `owl:sameAs` entailments | Emitted by `materialize()` ✓ |
|
|
535
|
+
| `owl:differentFrom` entailments | Not emitted (accepted as input ✓) |
|
|
536
|
+
| Data property assertions | Emitted by `materialize()` ✓ |
|
|
152
537
|
|
|
153
538
|
## Browser deployment
|
|
154
539
|
|
|
@@ -206,7 +591,7 @@ Benchmarked on an 8-core Linux host. Native = Konclude v0.7.0 Docker image; WASM
|
|
|
206
591
|
| LUBM schema | SHI | 307 | 34 ms | 207 ms | 202 ms | ~6× |
|
|
207
592
|
| GALEN | SHIF | 30 817 | 286 ms | 968 ms | 852 ms | ~3.4× |
|
|
208
593
|
| Roberts family | SROIQ | 3 866 | 2 118 ms | 38 769 ms | 37 903 ms | ~18× |
|
|
209
|
-
| LUBM schema + data | SHI | 100 850 | 1 017 ms | 1 672 ms |
|
|
594
|
+
| LUBM schema + data | SHI | 100 850 | 1 017 ms | 1 672 ms | 1 965 ms | ~1.6× |
|
|
210
595
|
|
|
211
596
|
¹ Native pipeline (OWL 2 XML parse + classify/realize). Native uses `classification` for
|
|
212
597
|
TBox-only ontologies and `realization` for ontologies with individuals (Roberts family,
|
|
@@ -214,14 +599,42 @@ LUBM + data) — matching WASM's operation selection. LUBM schema ratio is domin
|
|
|
214
599
|
fixed WASM startup cost (pthreads pool init) on a tiny 307-triple ontology.
|
|
215
600
|
|
|
216
601
|
² WASM timing covers binary buffer encode (Quads → buffer) + `loadTripleBuffer` + realization
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
(
|
|
602
|
+
|
|
603
|
+
- decode. Input RDF is pre-parsed into quads before the timing window — NTriples/Turtle parsing
|
|
604
|
+
is excluded, matching what your application pays after data is already loaded into a Store.
|
|
605
|
+
Node.js 20 and Chromium 135 are within ~12% of each other on most ontologies; Roberts family
|
|
606
|
+
(SROIQ with ABox realization) is effectively equal.
|
|
222
607
|
|
|
223
608
|
Run `npm run bench` to reproduce (requires a built WASM binary — see [Build from source](#build-from-source)).
|
|
224
609
|
|
|
610
|
+
### What each fixture tests
|
|
611
|
+
|
|
612
|
+
**LUBM schema** (SHI, TBox-only) — a shallow university-domain ontology: 49 classes, 25 object
|
|
613
|
+
properties, 36 subclass edges, one transitive property. No individuals. Konclude runs pure
|
|
614
|
+
classification; actual tableau work is trivial. The 207 ms is almost entirely pthread pool
|
|
615
|
+
startup on a 307-triple ontology.
|
|
616
|
+
|
|
617
|
+
**GALEN** (SHIF, TBox-only) — a medical terminology ontology: 4 740 classes, 413 object
|
|
618
|
+
properties, 150 functional properties, 26 transitive properties, and 3 446 existential
|
|
619
|
+
restrictions (`someValuesFrom`) cross-connected to 3 237 subclass edges. No individuals.
|
|
620
|
+
The dense restriction graph drives TBox saturation — constraints propagate across thousands
|
|
621
|
+
of interleaved concept/role pairs. SHIF adds functional property reasoning on top. This is
|
|
622
|
+
pure classification load.
|
|
623
|
+
|
|
624
|
+
**Roberts family** (SROIQ, TBox + ABox) — a genealogy ontology: 171 classes, 80 object
|
|
625
|
+
properties, 405 named individuals, 11 symmetric properties, 8 transitive properties, and
|
|
626
|
+
24 property chain axioms (`owl:propertyChainAxiom`). SROIQ is the full OWL 2 DL
|
|
627
|
+
expressiveness. The 405 individuals trigger ABox realization — Konclude computes the type
|
|
628
|
+
of every individual under every applicable concept while propagating role chains across the
|
|
629
|
+
family tree. Role chains require joining property paths, which multiplies the search space.
|
|
630
|
+
This is why 3 866 triples takes 38 s.
|
|
631
|
+
|
|
632
|
+
**LUBM schema + data** (SHI, TBox + ABox) — the same shallow TBox combined with ~25 000
|
|
633
|
+
individuals (students, professors, courses across multiple universities). SHI has no property
|
|
634
|
+
chains or nominals, so ABox realization is type propagation only: each individual is
|
|
635
|
+
classified under the existing concept hierarchy. Cost scales roughly linearly with individual
|
|
636
|
+
count rather than combinatorially, hence the 1.6× native ratio despite 25 000 instances.
|
|
637
|
+
|
|
225
638
|
## How it works
|
|
226
639
|
|
|
227
640
|
```text
|