mixle.reason.ontology module¶
Ontology objects for typed constraints on knowledge graphs.
A knowledge graph without a schema will happily assert (paris, employs, france). An
Ontology is the typed contract that rules such triples out structurally: a class hierarchy,
relation signatures (employs: Organization -> Person), per-relation axioms (functional, symmetric,
asymmetric, irreflexive), and disjoint-class declarations. Ontology.check_triple() names every
violation of one assertion; Ontology.check_graph() audits a whole triple set, including the
cross-triple axioms (a functional relation asserted with two different tails).
The same contract turns a fitted KG embedding into an ontology-constrained
distribution: OntologyConstrainedKG wraps a
KnowledgeGraphDistribution and masks the tail posterior to
range-conforming entities, renormalizing – so the model literally cannot place probability on a triple
the ontology forbids. Constrained extraction or decoding can apply the same
mask before accepting generated triples.
Everything is symbolic and dependency-free: entities/relations are strings, entity types are supplied
as a {entity: class} map (the entity-linking output). Violations are named, never silent.
Construction is collision- and cycle-safe (MXR-080-0297): Ontology.add_class()/add_relation
reject a name that is already registered instead of silently overwriting its definition, and
Ontology.replace_class()/replace_relation are the explicit, differently named operations for
deliberately redefining one – this codebase’s established reject-then-explicit-replace convention (see
e.g. CrossModalModel.add_modality/replace_modality, MXR-080-0276). Decode-time filtering and KG
completion validate the FULL contract – per-triple axioms plus cross-triple graph axioms plus the raw
model output itself – rather than the partial checks that let a schema-violating or numerically
degenerate result through while still claiming to be “ontology-constrained” (MXR-080-0298/0299/0300),
and constrained decoding validates its confidence policy and binds its identity to the result
(MXR-080-0301).
- class Ontology(classes=<factory>, relations=<factory>, axioms=<factory>, disjoint=<factory>, version=None)[source]
Bases:
objectA typed schema over knowledge: class hierarchy + relation signatures + axioms + disjointness.
- Parameters:
- add_class(name, parent=None)[source]
Add a NEW ontology class, optionally under a known parent.
MXR-080-0297: a duplicate
nameis rejected rather than silently overwriting the existing class’s parent – the prior behavior let a second, differently-parentedadd_class(name, other_parent)retroactively change what every relation/disjointness declaration mentioningnamemeans, with no trace that a redefinition happened. Usereplace_class()to deliberately reparent an existing class.- Raises:
ValueError –
nameis already registered, orparentis not a known class.- Parameters:
- Return type:
Ontology
- replace_class(name, parent=None)[source]
Deliberately reparent an existing class.
MXR-080-0297: unlike
add_class()(which rejects a duplicate name outright), this exists specifically to allow redefining an existing class’s parent – but aparentthat ISname, or that is already a descendant ofnamein the CURRENT hierarchy, is rejected here, before the mutation, rather than silently installed and merely limped around later byis_a()’s cycle guard (a partial-answer, quietly-wrong result).- Raises:
KeyError –
nameis not already registered.ValueError –
parentis not a known class, or reparenting would create a self-loop or a cycle.
- Parameters:
- Return type:
Ontology
- add_relation(name, domain, range_, *axioms)[source]
Add a NEW relation with domain, range, and optional ontology axioms.
MXR-080-0297: a duplicate
nameis rejected rather than silently overwriting the existing relation’s signature and axioms. Usereplace_relation()to deliberately redefine an existing one.- Raises:
ValueError –
nameis already registered,domain/range_is not a known class, or an axiom is not one ofAXIOMS.- Parameters:
- Return type:
Ontology
- replace_relation(name, domain, range_, *axioms)[source]
Deliberately redefine an existing relation’s domain, range, and/or axioms.
MXR-080-0297: the explicit, differently-named counterpart to
add_relation()’s reject-on-duplicate.
- add_disjoint(a, b)[source]
Declare two classes mutually exclusive.
MXR-080-0297: an unknown class reference, or declaring a class disjoint with ITSELF, is rejected rather than silently accepted –
add_disjoint(x, x)previously madecheck_triple()flag every single instance ofxas violating disjointness against itself, and an unknown class reference could never match any real entity, silently defeating the declaration instead of failing loudly at the typo.- Raises:
ValueError –
aorbis not a known class, ora == b.- Parameters:
- Return type:
Ontology
- is_a(cls, ancestor)[source]
Whether
clsisancestoror a descendant of it (walks the parent chain).MXR-080-0297: construction (
add_class()/replace_class()) now rejects every path that could install a cycle, so this walk should never see one. As a defense-in-depth check (e.g. against.classesbeing mutated directly, bypassing the API) this RAISES if a cycle is nonetheless encountered, rather than silently stopping and returning a partial, possibly wrong answer – a malformed schema must never look like an ordinary “not an ancestor”.
- check_triple(h, r, t, types)[source]
Every named violation of
(h, r, t)given entitytypes({} means unconstrained).
- check_graph(triples, types)[source]
Audit a triple set: per-triple violations plus the cross-triple axioms (functional/symmetric/ asymmetric/transitive).
Returns
{consistent, n_triples, violations: [{triple, problems}]}– every problem named. Every cross-triple (graph-level) violation also carries an"axiom"key naming which axiom it is (the per-triple violations sourced fromcheck_triple()do not, since one of those can bundle several different kinds of problem for a single triple) –filter_triples()relies on this to apply a deterministic policy per axiom kind (MXR-080-0298).
- filter_triples(triples, types)[source]
Split triples into (kept, rejected-with-reasons) – the decode mask.
MXR-080-0298: per-triple consistency (
check_triple()) alone is not enough – it cannot see that a functional relation was asserted with two different tails, or any other cross-triple axiom violation, sokeptcould previously contain a set that is globally inconsistent even though every individual triple in it looks fine alone. After the per-triple pass, the accepted set is re-validated AS A GRAPH and any set-level conflict is resolved by_resolve_graph_conflicts()’s deterministic policy; every triple that policy withholds is added torejectedwith its reason, same as a per-triple violation.
- class OntologyConstrainedKG(kg, ontology, *, entities, relations, types)[source]
Bases:
objectA fitted KG embedding, typed by an ontology: probability mass only on schema-consistent triples.
Wraps a
KnowledgeGraphDistribution(entities and relations as integer indices) together with the symbolic ontology and the index<->name maps. The tail posterior is masked to entities that satisfy the FULL triple contract for the completion – range, the head’s domain, irreflexivity, and disjointness (MXR-080-0299) – over a validated, numerically-stable-normalized model output (MXR-080-0300), and renormalized, so completion can never propose an ontology-violating tail nor silently degrade into NaN “probabilities” –Graph(ontology)as a distribution.- Parameters:
- tail_posterior(head, relation)[source]
p(tail | head, relation)over ONLY entities satisfying the full triple contract for this completion (renormalized).MXR-080-0299: previously masked on the candidate tail’s range class alone, so e.g. an irreflexive relation’s self-completion competed for probability mass on equal footing with every genuinely valid tail – for one such relation, self-completion was assigned the LARGEST probability of any candidate. The mask now also checks the head’s domain, irreflexivity, and disjointness.
MXR-080-0300: the raw model output is validated before it is ever normalized – a NaN, +inf, wrong-length, or non-vector
tail_log_posteriorraises instead of silently producing a dictionary of NaN “probabilities”. Returns{}when the ontology structurally admits no candidate for this completion (an ordinary, expected outcome – e.g. an empty range class), but RAISES when the schema admits candidates and every one of them is nonetheless degenerate (-inf/ carries no finite mass): that is an anomalous model/data failure, not a schema fact, and returning{}for both would hide the difference.
- class ConstrainedDecode(facts, rejected, below_floor, n_samples, receipt=<factory>)[source]
Bases:
objectThe result of ontology-constrained LLM decoding: what survived, what the schema rejected, and why.
- Parameters:
- asserted()[source]
Return facts that passed constraints and confidence floor.
- constrained_decode(llm, prompt, ontology, types, *, n=None, floor=0.5, calibrator=None, calibration_population=None)[source]
Decode only schema-consistent facts above a confidence floor.
Samples
llm(aGraphLLM)ntimes, masks every sampled graph throughOntology.filter_triples()(violating triples are rejected with named reasons), then marginalizes the constrained graphs into aGraphDistributionand keeps only facts whose edge marginal clearsfloor– the calibrated confidence floor (pass a fittedcalibratorfromfit_fact_calibrator()to apply the floor on calibrated truth probability rather than the raw marginal). Consistent-but-underconfident facts are reported as withheld, never silently dropped: the decode says what it refused to assert and why.MXR-080-0298: per-sample filtering only guarantees each INDIVIDUAL sampled graph is internally consistent; the published
factsset is built by thresholding each triple’s marginal independently, so two triples that never co-occurred in any single sample (e.g. two different tails of a functional relation, each confident in a disjoint subset of samples) could previously both clear the floor and be asserted together – globally inconsistent while still labeled “ontology-constrained”.factsis re-validated as a graph after confidence-sorting, soOntology.filter_triples()’s “first in the list wins” conflict policy resolves to “highest-confidence wins” at this aggregate stage; every casualty is named inrejected.MXR-080-0301:
floorand every calibrated confidence must be finite and within[0, 1]– see_require_unit_probability()for why these raise rather than silently clip or misclassify. The result’sreceiptbinds the calibrator’s identity (its class and, when available, itsmethod), thecalibration_populationthe caller asserts it was fit on (this function cannot recover that from an arbitrary calibrator object, so it is taken on trust from the caller – pass whatever identifies the labeled set used withfit_fact_calibrator()), andontology.version, so a result can be independently audited instead of trusted blind.