Infon HypergraphReasoner
Sheaf-aware typed R-GCN over the infon hypergraph that produces
per-node DempsterβShafer masses, then routes them through a tree of
learned IKL operators (that, not, and, or, if, iff,
forall, exists, ist) to answer compositional, IKL-style queries.
Three optional question heads (next_anchor, anomaly, role_type)
expose the same per-node features for retrieval / triage tasks.
The model is the symbolic-reasoning end-piece of the infon stack β
upstream stages (extractor, coref, sheaf-GNN) build the hypergraph and
hand it to this network as (nodeFeatures, edgeIndex, edgeTypes, edgeWeights, situationFeatures) tensors.
Quick start (JavaScript)
npm install @cp500/infon-reasoner onnxruntime-web
import { ReasonerModel } from '@cp500/infon-reasoner';
const model = await ReasonerModel.fromHub('cp500/infon-reasoner', {
precision: 'fp16', // half size, default in browsers
device: 'auto',
});
// `graph` is the tensor triple produced by HypergraphBuilder upstream.
const query = {
op: 'forall',
args: [{
op: 'and',
args: [
{ op: 'that', args: [{ kind: 'triple', subject: 'x',
predicate: 'causes', object: 'Y' }] },
{ op: 'not', args: [{ op: 'that', args: [{ kind: 'triple',
subject: 'x', predicate: 'retracted',
object: 'Y' }] }] },
],
}],
};
const r = await model.reason(graph, query);
console.log(r.verdict); // 'SUPPORTS' | 'REFUTES' | 'NEI'
console.log(r.mass); // DS mass over {S, R, NEI}
console.log(r.heads?.anomaly?.[0]); // optional per-node anomaly
The JS client source is mirrored under js/ for
self-contained installs (vendored alongside its sibling deps atom,
ds, runtime).
Architecture
HyperGraph (nodeFeatures, edgeIndex, edgeTypes, edgeWeights,
situationFeatures?)
β
βΌ
TypedRGCN backbone (hidden_dim=64, n_edge_types=β)
β’ per-relation-type linear maps
β’ sheaf restriction stalks per edge
β’ N rounds of message passing + residual updates
β
βΌ
MassReadout per node βββΊ perNode: MassFunction[]
{m(S), m(R), m(NEI), m(uncertain)}
β
βΌ composer.ts walks the operator tree
IKLComposer (9 ONNX ops) βββΊ mass: MassFunction
that β not β and β or β if β iff β forall β exists β ist
β
βΌ
Argmax over m(S)/m(R)/m(NEI) βββΊ verdict: SUPPORTS | REFUTES | NEI
(parallel)
TypedRGCN backbone last layer
βββΊ next_anchor head (anchor logits)
βββΊ anomaly head (per-node score in [0,1])
βββΊ role_type head (per-node role logits)
The sheaf design types messages by edge type and situation vector,
not by predicate slug β which is what makes the reasoner transfer
across relation vocabularies. situation_dim=16.
IKL composition contract
Queries are JSON operator-trees. The leaf is that(atom) where
atom = {kind: 'triple', subject, predicate, object}; every other
node wraps one or two child queries. Variables in the subject/object
slots (x, Y) are bound by forall / exists against the node ids
present in the graph.
Full example β for every x, x causes Y holds and was not later
retracted:
const query = {
op: 'forall',
args: [{
op: 'and',
args: [
{ op: 'that', args: [{ kind: 'triple', subject: 'x',
predicate: 'causes', object: 'Y' }] },
{ op: 'not', args: [{ op: 'that', args: [{ kind: 'triple',
subject: 'x', predicate: 'retracted',
object: 'Y' }] }] },
],
}],
};
The composer walks the tree post-order, dispatching each node to its ONNX session. Each operator consumes child masses + the relevant graph slice and emits a refined mass; the root's output is the final verdict.
Files
Backbone
| File | Size | Purpose |
|---|---|---|
onnx/backbone.onnx |
407 KB | Sheaf R-GCN backbone (FP32) |
onnx/backbone.onnx.data |
β | External weights for backbone FP32 |
onnx/backbone.fp16.onnx |
223 KB | Backbone (FP16, default for browser) |
onnx/backbone.fp16.onnx.data |
β | External weights for backbone FP16 |
IKL operators
| File | Size | Purpose |
|---|---|---|
onnx/ikl_that.onnx |
37 KB | IKL that operator (FP32) |
onnx/ikl_that.fp16.onnx |
19 KB | IKL that operator (FP16) |
onnx/ikl_not.onnx |
37 KB | IKL not operator (FP32) |
onnx/ikl_not.fp16.onnx |
19 KB | IKL not operator (FP16) |
onnx/ikl_and.onnx |
38 KB | IKL and operator (FP32) |
onnx/ikl_and.fp16.onnx |
21 KB | IKL and operator (FP16) |
onnx/ikl_or.onnx |
38 KB | IKL or operator (FP32) |
onnx/ikl_or.fp16.onnx |
21 KB | IKL or operator (FP16) |
onnx/ikl_if.onnx |
72 KB | IKL if operator (FP32) |
onnx/ikl_if.fp16.onnx |
38 KB | IKL if operator (FP16) |
onnx/ikl_iff.onnx |
106 KB | IKL iff operator (FP32) |
onnx/ikl_iff.fp16.onnx |
56 KB | IKL iff operator (FP16) |
onnx/ikl_forall.onnx |
38 KB | IKL forall operator (FP32) |
onnx/ikl_forall.fp16.onnx |
21 KB | IKL forall operator (FP16) |
onnx/ikl_exists.onnx |
38 KB | IKL exists operator (FP32) |
onnx/ikl_exists.fp16.onnx |
21 KB | IKL exists operator (FP16) |
onnx/ikl_ist.onnx |
25 KB | IKL ist operator (FP32) |
onnx/ikl_ist.fp16.onnx |
13 KB | IKL ist operator (FP16) |
Question heads
| File | Size | Purpose |
|---|---|---|
onnx/head_next_anchor.onnx |
1 KB | next_anchor head (FP32) |
onnx/head_next_anchor.fp16.onnx |
1 KB | next_anchor head (FP16) |
onnx/head_anomaly.onnx |
9 KB | anomaly head (FP32) |
onnx/head_anomaly.fp16.onnx |
6 KB | anomaly head (FP16) |
onnx/head_role_type.onnx |
35 KB | role_type head (FP32) |
onnx/head_role_type.fp16.onnx |
18 KB | role_type head (FP16) |
The meta.json next to the ONNX files keys logical names
(backbone_fp16, ikl_and_fp32, head_anomaly_fp16, β¦) to file
paths, so the JS client doesn't hard-code any layout. Re-training keeps
the .pt snapshots under pytorch/.
Evaluation
Validation metrics from the default Tier 6 wave 1 training run on the synthetic hypergraph corpus.
IKL operators (val MSE on per-node DS mass)
| IKL op | Val MSE |
|---|---|
that |
β |
not |
β |
and |
β |
or |
β |
if |
β |
iff |
β |
forall |
β |
exists |
β |
ist |
β |
Question heads (val accuracy)
| Head | Val acc |
|---|---|
next_anchor |
β |
anomaly |
β |
role_type |
β |
Limitations
- Synthetic data only. All training uses the procedurally generated
hypergraph corpus from
synthgen_hypergraph.py. Real-world graphs with adversarial / noisy structure are not yet validated; expect domain shift on free-text-derived graphs. - MVP head set. Only three question heads ship in this release
(
next_anchor,anomaly,role_type). The architecture admits more, but they are not yet trained. Untrained heads should be treated as unavailable. - Operator coverage = the 9 IKL connectives above. Anything outside
that set must be expressed by composition; the composer raises a
programmer-error if it encounters an unknown
optag. - No long-range backtracking. The reasoner is a forward-pass
composer, not a search procedure. Pair it with
@cp500/infon's GraphMCTS (./mcts) for tasks that need multi-step abductive search. forall/existsresolve over node ids. If the upstream graph drops a candidate (e.g. coref merged it into another mention) the quantifier sees fewer bindings than a human reader would.
License
Apache 2.0 for both weights and JS client code.