TOMK / src /python /tom_learner05 /learner.py
c0mbatduckzz's picture
Publish WQK 0.6 formal specification, use cases and verified release (part 2)
767a051 verified
Raw History Blame Contribute Delete
13.3 kB
"""Deterministic observation-to-hypothesis pipeline for TOM Learner 0.1."""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
from typing import Any, Iterable, Mapping
from tom_world03.canonical import attach_hash, canonical_bytes, require_hash
from tom_world03.rational import Q
from .affine import (
candidate_coefficients,
counterexamples_from_evidence,
derive_candidates,
evaluate_candidate,
find_contradictions,
select_from_training,
)
from .model import ObservationSet, PROFILE
from .split import deterministic_split
DECISION_SCHEMA = "TOM-LEARNER-ACCEPTANCE-DECISION-0.1"
CERTIFICATE_SCHEMA = "TOM-LEARNING-CERTIFICATE-0.1"
LEARNED_DEFINITION_SCHEMA = "TOM-LEARNED-AFFINE-DEFINITION-0.1"
REJECTION_LINEAGE_SCHEMA = "TOM-REJECTED-CANDIDATE-LINEAGE-0.1"
def _hash_values(value: Any) -> str:
return "sha256:" + hashlib.sha256(canonical_bytes(value)).hexdigest()
def _selected_candidate(
selection: Mapping[str, Any],
candidates: Iterable[Mapping[str, Any]],
) -> Mapping[str, Any] | None:
selected_hash = selection.get("selected_candidate_hash")
if selected_hash is None:
return None
for candidate in candidates:
if candidate.get("content_hash") == selected_hash:
return candidate
raise ValueError("selection references a candidate absent from enumeration")
def _evidence_for(
candidate: Mapping[str, Any] | None,
candidates: list[Mapping[str, Any]],
evidence: list[Mapping[str, Any]],
) -> Mapping[str, Any] | None:
if candidate is None:
return evidence[0] if evidence else None
wanted = candidate["content_hash"]
for item in evidence:
if item.get("candidate_hash") == wanted:
return item
raise ValueError("selected candidate has no residual evidence")
def _decision(
dataset: ObservationSet,
split: Mapping[str, Any],
selection: Mapping[str, Any],
candidate: Mapping[str, Any] | None,
evidence: Mapping[str, Any] | None,
contradictions: list[Mapping[str, Any]],
) -> dict[str, Any]:
reasons: list[str] = []
checks: list[dict[str, Any]] = []
def check(name: str, passed: bool, detail: str) -> None:
checks.append({"name": name, "passed": bool(passed), "detail": detail})
if not passed:
reasons.append(detail)
counts = split["counts"]
check("minimum training observations", int(counts["train"]) >= 2,
"training split has fewer than two observations")
check("minimum validation observations", int(counts["validation"]) >= 1,
"validation split is empty")
check("minimum holdout observations", int(counts["holdout"]) >= 1,
"holdout split is empty")
check("selected exact training candidate", candidate is not None,
str(selection.get("reason", "no exact training candidate")))
if candidate is not None:
check("model complexity",
int(candidate["complexity"]) <= dataset.acceptance_policy.max_model_complexity,
"selected model exceeds the declared complexity budget")
else:
check("model complexity", False, "no selected model exists")
if evidence is None:
check("exact training residuals", False, "no residual evidence exists")
check("exact validation residuals", False, "no validation residual evidence exists")
check("exact holdout residuals", False, "no holdout residual evidence exists")
else:
train_exact = bool(evidence["splits"]["train"]["metrics"]["exact"])
validation_exact = bool(evidence["splits"]["validation"]["metrics"]["exact"])
holdout_exact = bool(evidence["splits"]["holdout"]["metrics"]["exact"])
check("exact training residuals", train_exact,
"selected/best candidate has a nonzero training residual")
check("exact validation residuals",
validation_exact or not dataset.acceptance_policy.require_exact_validation,
"selected candidate has a nonzero validation residual")
check("exact holdout residuals",
holdout_exact or not dataset.acceptance_policy.require_exact_holdout,
"selected candidate has a nonzero holdout residual")
check("no exact-input contradictions",
not contradictions or not dataset.acceptance_policy.reject_contradictions,
"observation set contains contradictory exact outputs for one input")
accepted = all(item["passed"] for item in checks)
return attach_hash({
"schema": DECISION_SCHEMA,
"profile": PROFILE,
"observation_set_id": dataset.id,
"observation_set_hash": dataset.content_hash,
"split_certificate_hash": split["content_hash"],
"acceptance_policy_hash": dataset.acceptance_policy.content_hash,
"selection_certificate_hash": selection["content_hash"],
"selected_candidate_hash": None if candidate is None else candidate["content_hash"],
"evidence_hash": None if evidence is None else evidence["content_hash"],
"contradiction_hashes": [item["content_hash"] for item in contradictions],
"checks": checks,
"accepted": accepted,
"reasons": reasons,
"policy_boundary": {
"fit_and_selection_splits": ["train"],
"validation_role": "acceptance gate only",
"holdout_role": "final acceptance audit only",
},
})
def _learned_definition(
dataset: ObservationSet,
candidate: Mapping[str, Any],
certificate_hash: str,
) -> dict[str, Any]:
a, b = candidate_coefficients(candidate)
suffix = hashlib.sha256(canonical_bytes({
"dataset": dataset.id,
"candidate": candidate["content_hash"],
})).hexdigest()[:24]
return attach_hash({
"schema": LEARNED_DEFINITION_SCHEMA,
"profile": PROFILE,
"id": f"definition:learned-affine:{suffix}",
"kind": "learned-exact-affine-relation",
"domain": {
"input": dataset.input_name,
"output": dataset.output_name,
"numeric": "exact-rational",
},
"codomain": "exact-rational-residual",
"relation_interface": "SDF0@Def",
"model": "y=a*t+b",
"coefficients": {"a": a.to_record(), "b": b.to_record()},
"expression": {
"op": "sub",
"args": [
{"op": "field", "name": dataset.output_name},
{
"op": "add",
"args": [
{
"op": "mul",
"args": [
{"op": "const", "value": a.to_record()},
{"op": "field", "name": dataset.input_name},
],
},
{"op": "const", "value": b.to_record()},
],
},
],
},
"zero_locus": f"{dataset.output_name} = a*{dataset.input_name} + b",
"source_observation_set_hash": dataset.content_hash,
"source_candidate_hash": candidate["content_hash"],
"source_learning_certificate_hash": certificate_hash,
"base_world_hash": dataset.base_world_hash,
"base_handoff_hash": dataset.base_handoff_hash,
"provenance": {
"class": "deterministically-induced-and-held-out-verified",
"learner": PROFILE,
},
})
@dataclass(frozen=True, slots=True)
class LearningRun:
dataset: ObservationSet
split_certificate: Mapping[str, Any]
candidates: tuple[Mapping[str, Any], ...]
enumeration: Mapping[str, Any]
evidence: tuple[Mapping[str, Any], ...]
selection: Mapping[str, Any]
contradictions: tuple[Mapping[str, Any], ...]
counterexamples: tuple[Mapping[str, Any], ...]
decision: Mapping[str, Any]
certificate: Mapping[str, Any]
learned_definition: Mapping[str, Any] | None
rejection_lineage: Mapping[str, Any] | None
@property
def accepted(self) -> bool:
return bool(self.decision["accepted"])
def all_records(self) -> list[Mapping[str, Any]]:
values: list[Mapping[str, Any]] = [
*self.dataset.source_records(),
self.split_certificate,
*self.candidates,
self.enumeration,
*self.evidence,
self.selection,
*self.contradictions,
*self.counterexamples,
self.decision,
self.certificate,
]
if self.learned_definition is not None:
values.append(self.learned_definition)
if self.rejection_lineage is not None:
values.append(self.rejection_lineage)
return values
def summary(self) -> dict[str, Any]:
candidate = self.learned_definition
return {
"observation_set_id": self.dataset.id,
"observation_set_hash": self.dataset.content_hash,
"accepted": self.accepted,
"selected_candidate_hash": self.selection.get("selected_candidate_hash"),
"learned_definition_hash": None if candidate is None else candidate["content_hash"],
"candidate_count": len(self.candidates),
"counterexample_count": len(self.counterexamples),
"contradiction_count": len(self.contradictions),
"decision_hash": self.decision["content_hash"],
"certificate_hash": self.certificate["content_hash"],
}
def learn_observation_set(dataset: ObservationSet) -> LearningRun:
split = deterministic_split(dataset)
candidates, enumeration = derive_candidates(dataset, split)
evidence = [evaluate_candidate(dataset, split, candidate) for candidate in candidates]
selection = select_from_training(dataset, split, candidates, evidence)
selected = _selected_candidate(selection, candidates)
selected_evidence = _evidence_for(selected, candidates, evidence)
contradictions = find_contradictions(dataset)
counterexamples = [] if selected_evidence is None else counterexamples_from_evidence(dataset, selected_evidence)
decision = _decision(dataset, split, selection, selected, selected_evidence, contradictions)
certificate = attach_hash({
"schema": CERTIFICATE_SCHEMA,
"profile": PROFILE,
"observation_set_id": dataset.id,
"observation_set_hash": dataset.content_hash,
"base_world_hash": dataset.base_world_hash,
"base_handoff_hash": dataset.base_handoff_hash,
"split_certificate_hash": split["content_hash"],
"assignment_basis_hash": split["assignment_basis_hash"],
"candidate_enumeration_hash": enumeration["content_hash"],
"fit_input_hash": enumeration["fit_input_hash"],
"selection_certificate_hash": selection["content_hash"],
"selected_candidate_hash": selection.get("selected_candidate_hash"),
"selected_evidence_hash": None if selected_evidence is None else selected_evidence["content_hash"],
"contradiction_hashes": [item["content_hash"] for item in contradictions],
"counterexample_hashes": [item["content_hash"] for item in counterexamples],
"acceptance_decision_hash": decision["content_hash"],
"accepted": decision["accepted"],
"phase_trace": [
"parse-observations",
"deterministic-id-only-split",
"fit-train-only",
"select-train-only",
"validation-gate",
"holdout-audit",
"contradiction-gate",
"candidate-certificate",
],
"causal_boundary": "certificate is a proposal record; it is not authoritative world state",
})
learned_definition = None
rejection_lineage = None
if decision["accepted"]:
if selected is None:
raise AssertionError("accepted decision has no selected candidate")
learned_definition = _learned_definition(dataset, selected, certificate["content_hash"])
else:
rejection_lineage = attach_hash({
"schema": REJECTION_LINEAGE_SCHEMA,
"profile": PROFILE,
"observation_set_id": dataset.id,
"observation_set_hash": dataset.content_hash,
"learning_certificate_hash": certificate["content_hash"],
"acceptance_decision_hash": decision["content_hash"],
"candidate_hashes": [candidate["content_hash"] for candidate in candidates],
"counterexample_hashes": [item["content_hash"] for item in counterexamples],
"contradiction_hashes": [item["content_hash"] for item in contradictions],
"reasons": list(decision["reasons"]),
"retention": "rejected candidates and counterexamples remain in append-only lineage",
})
return LearningRun(
dataset=dataset,
split_certificate=split,
candidates=tuple(candidates),
enumeration=enumeration,
evidence=tuple(evidence),
selection=selection,
contradictions=tuple(contradictions),
counterexamples=tuple(counterexamples),
decision=decision,
certificate=certificate,
learned_definition=learned_definition,
rejection_lineage=rejection_lineage,
)