TOMK / src /python /tom_learner05 /model.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.9 kB
"""Typed, content-addressed records for TOM Learner 0.1.
The authority boundary is deliberately explicit:
observations -> deterministic split -> candidate hypotheses -> evidence
-> acceptance decision -> optional learned definition -> promotion
The learner never mutates the base world directly. A candidate can become
visible in the authoritative learner overlay only through ``LearnerStore`` and
a parent-bound promotion transaction.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Sequence
from tom_world03.canonical import require_hash
from tom_world03.rational import Q
CANONICAL_SEED_SHA256 = "d1417a3136772c0cf3eddcd4962ce07d42cbf87616f7b5bae09fc652d9b807b5"
BASE_WORLD_HASH = "sha256:c25c99eeeb728f50b52a06e89b9f669470ccb294fd8f0cc6e4ccb87bde9ff2d9"
BASE_HANDOFF_HASH = "sha256:3d2b46cfd33ba6e5cf0a13697fb59e374a64ad30450fdd3c256c98a04ebc474b"
PROFILE = "TOM-LEARNER-0.1"
OBSERVATION_SCHEMA = "TOM-EXACT-OBSERVATION-0.1"
OBSERVATION_SET_SCHEMA = "TOM-AFFINE-OBSERVATION-SET-0.1"
SPLIT_POLICY_SCHEMA = "TOM-LEARNER-SPLIT-POLICY-0.1"
HYPOTHESIS_FAMILY_SCHEMA = "TOM-AFFINE-HYPOTHESIS-FAMILY-0.1"
ACCEPTANCE_POLICY_SCHEMA = "TOM-LEARNER-ACCEPTANCE-POLICY-0.1"
SPLITS = ("train", "validation", "holdout")
def _require_nonempty(value: Any, label: str) -> str:
text = str(value)
if not text:
raise ValueError(f"{label} must be nonempty")
return text
def _require_hash_text(value: Any, label: str) -> str:
text = str(value)
if not text.startswith("sha256:") or len(text) != 71:
raise ValueError(f"{label} must be a sha256: content hash")
try:
int(text[7:], 16)
except ValueError as exc:
raise ValueError(f"{label} must contain hexadecimal SHA-256") from exc
if text[7:] != text[7:].lower():
raise ValueError(f"{label} must use lowercase hexadecimal")
return text
def _positive_integer(value: Any, label: str, *, minimum: int = 1) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
raise ValueError(f"{label} must be an integer >= {minimum}")
return value
@dataclass(frozen=True, slots=True)
class Observation:
id: str
t: Q
y: Q
provenance: Mapping[str, Any]
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "Observation":
require_hash(record, label="observation")
if record.get("schema") != OBSERVATION_SCHEMA:
raise ValueError(f"observation schema must be {OBSERVATION_SCHEMA}")
provenance = record.get("provenance")
if not isinstance(provenance, Mapping):
raise ValueError("observation provenance must be an object")
return cls(
id=_require_nonempty(record.get("id", ""), "observation id"),
t=Q.from_value(record.get("t")),
y=Q.from_value(record.get("y")),
provenance=dict(provenance),
content_hash=_require_hash_text(record.get("content_hash"), "observation content_hash"),
)
def to_record(self) -> dict[str, Any]:
return {
"schema": OBSERVATION_SCHEMA,
"id": self.id,
"t": self.t.to_record(),
"y": self.y.to_record(),
"provenance": dict(self.provenance),
"content_hash": self.content_hash,
}
@dataclass(frozen=True, slots=True)
class SplitPolicy:
strategy: str
ratios: Mapping[str, int]
minimum_counts: Mapping[str, int]
salt: str
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "SplitPolicy":
require_hash(record, label="split policy")
if record.get("schema") != SPLIT_POLICY_SCHEMA:
raise ValueError(f"split policy schema must be {SPLIT_POLICY_SCHEMA}")
strategy = str(record.get("strategy", ""))
if strategy != "sha256-id-order-largest-remainder":
raise ValueError("unsupported split strategy")
ratios_raw = record.get("ratios")
minima_raw = record.get("minimum_counts")
if not isinstance(ratios_raw, Mapping) or set(ratios_raw) != set(SPLITS):
raise ValueError("split ratios must name train, validation, and holdout exactly")
if not isinstance(minima_raw, Mapping) or set(minima_raw) != set(SPLITS):
raise ValueError("minimum_counts must name train, validation, and holdout exactly")
ratios = {name: _positive_integer(ratios_raw[name], f"ratio {name}") for name in SPLITS}
minima = {name: _positive_integer(minima_raw[name], f"minimum {name}", minimum=0) for name in SPLITS}
if minima["train"] < 2:
raise ValueError("training minimum must be at least two")
return cls(
strategy=strategy,
ratios=ratios,
minimum_counts=minima,
salt=_require_nonempty(record.get("salt", ""), "split salt"),
content_hash=_require_hash_text(record.get("content_hash"), "split policy hash"),
)
def to_record(self) -> dict[str, Any]:
return {
"schema": SPLIT_POLICY_SCHEMA,
"strategy": self.strategy,
"ratios": {name: int(self.ratios[name]) for name in SPLITS},
"minimum_counts": {name: int(self.minimum_counts[name]) for name in SPLITS},
"salt": self.salt,
"content_hash": self.content_hash,
}
@dataclass(frozen=True, slots=True)
class HypothesisFamily:
model: str
candidate_source: str
max_candidates: int
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "HypothesisFamily":
require_hash(record, label="hypothesis family")
if record.get("schema") != HYPOTHESIS_FAMILY_SCHEMA:
raise ValueError(f"hypothesis family schema must be {HYPOTHESIS_FAMILY_SCHEMA}")
if record.get("model") != "y=a*t+b":
raise ValueError("TOM Learner 0.1 supports only y=a*t+b")
source = str(record.get("candidate_source", ""))
if source != "all-distinct-t-unordered-training-pairs":
raise ValueError("unsupported affine candidate source")
return cls(
model="y=a*t+b",
candidate_source=source,
max_candidates=_positive_integer(record.get("max_candidates"), "max_candidates"),
content_hash=_require_hash_text(record.get("content_hash"), "hypothesis family hash"),
)
def to_record(self) -> dict[str, Any]:
return {
"schema": HYPOTHESIS_FAMILY_SCHEMA,
"model": self.model,
"candidate_source": self.candidate_source,
"max_candidates": self.max_candidates,
"content_hash": self.content_hash,
}
@dataclass(frozen=True, slots=True)
class AcceptancePolicy:
require_unique_exact_train: bool
require_exact_validation: bool
require_exact_holdout: bool
reject_contradictions: bool
max_model_complexity: int
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "AcceptancePolicy":
require_hash(record, label="acceptance policy")
if record.get("schema") != ACCEPTANCE_POLICY_SCHEMA:
raise ValueError(f"acceptance policy schema must be {ACCEPTANCE_POLICY_SCHEMA}")
bool_fields = (
"require_unique_exact_train",
"require_exact_validation",
"require_exact_holdout",
"reject_contradictions",
)
for field in bool_fields:
if not isinstance(record.get(field), bool):
raise ValueError(f"acceptance policy {field} must be boolean")
return cls(
require_unique_exact_train=bool(record["require_unique_exact_train"]),
require_exact_validation=bool(record["require_exact_validation"]),
require_exact_holdout=bool(record["require_exact_holdout"]),
reject_contradictions=bool(record["reject_contradictions"]),
max_model_complexity=_positive_integer(record.get("max_model_complexity"), "max_model_complexity"),
content_hash=_require_hash_text(record.get("content_hash"), "acceptance policy hash"),
)
def to_record(self) -> dict[str, Any]:
return {
"schema": ACCEPTANCE_POLICY_SCHEMA,
"require_unique_exact_train": self.require_unique_exact_train,
"require_exact_validation": self.require_exact_validation,
"require_exact_holdout": self.require_exact_holdout,
"reject_contradictions": self.reject_contradictions,
"max_model_complexity": self.max_model_complexity,
"content_hash": self.content_hash,
}
@dataclass(frozen=True, slots=True)
class ObservationSet:
id: str
seed_sha256: str
base_world_hash: str
base_handoff_hash: str
input_name: str
output_name: str
observations: tuple[Observation, ...]
split_policy: SplitPolicy
hypothesis_family: HypothesisFamily
acceptance_policy: AcceptancePolicy
provenance: Mapping[str, Any]
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "ObservationSet":
require_hash(record, label="observation set")
if record.get("schema") != OBSERVATION_SET_SCHEMA:
raise ValueError(f"observation-set schema must be {OBSERVATION_SET_SCHEMA}")
if record.get("profile") != PROFILE:
raise ValueError(f"observation-set profile must be {PROFILE}")
seed = str(record.get("seed_sha256", ""))
if seed != CANONICAL_SEED_SHA256:
raise ValueError("observation set is not bound to the canonical TOM seed")
base = _require_hash_text(record.get("base_world_hash"), "base_world_hash")
if base != BASE_WORLD_HASH:
raise ValueError("observation set is bound to an unsupported base world")
handoff = _require_hash_text(record.get("base_handoff_hash"), "base_handoff_hash")
if handoff != BASE_HANDOFF_HASH:
raise ValueError("observation set is bound to an unsupported literal handoff")
domain = record.get("domain")
if not isinstance(domain, Mapping) or domain.get("numeric") != "exact-rational":
raise ValueError("observation-set domain must declare exact-rational numeric semantics")
values = record.get("observations")
if not isinstance(values, list) or not values:
raise ValueError("observation set requires a nonempty observations array")
observations = tuple(Observation.from_record(value) for value in values)
ids = [item.id for item in observations]
if len(ids) != len(set(ids)):
raise ValueError("observation IDs must be unique")
if ids != sorted(ids):
raise ValueError("observations must be sorted by ID")
provenance = record.get("provenance")
if not isinstance(provenance, Mapping):
raise ValueError("observation-set provenance must be an object")
split = record.get("split_policy")
family = record.get("hypothesis_family")
policy = record.get("acceptance_policy")
if not all(isinstance(value, Mapping) for value in (split, family, policy)):
raise ValueError("observation-set policies must be objects")
return cls(
id=_require_nonempty(record.get("id", ""), "observation-set id"),
seed_sha256=seed,
base_world_hash=base,
base_handoff_hash=handoff,
input_name=_require_nonempty(domain.get("input", ""), "input field name"),
output_name=_require_nonempty(domain.get("output", ""), "output field name"),
observations=observations,
split_policy=SplitPolicy.from_record(split),
hypothesis_family=HypothesisFamily.from_record(family),
acceptance_policy=AcceptancePolicy.from_record(policy),
provenance=dict(provenance),
content_hash=_require_hash_text(record.get("content_hash"), "observation-set hash"),
)
def observation_map(self) -> dict[str, Observation]:
return {item.id: item for item in self.observations}
def to_record(self) -> dict[str, Any]:
return {
"schema": OBSERVATION_SET_SCHEMA,
"profile": PROFILE,
"id": self.id,
"seed_sha256": self.seed_sha256,
"base_world_hash": self.base_world_hash,
"base_handoff_hash": self.base_handoff_hash,
"domain": {
"input": self.input_name,
"output": self.output_name,
"numeric": "exact-rational",
},
"observations": [item.to_record() for item in self.observations],
"split_policy": self.split_policy.to_record(),
"hypothesis_family": self.hypothesis_family.to_record(),
"acceptance_policy": self.acceptance_policy.to_record(),
"provenance": dict(self.provenance),
"content_hash": self.content_hash,
}
def source_records(self) -> list[Mapping[str, Any]]:
"""Return every literal content-addressed input record in causal order."""
return [
self.split_policy.to_record(),
self.hypothesis_family.to_record(),
self.acceptance_policy.to_record(),
*(item.to_record() for item in self.observations),
self.to_record(),
]
def require_dataset_sequence(values: Any) -> Sequence[Mapping[str, Any]]:
if not isinstance(values, list) or not values:
raise ValueError("benchmark datasets must be a nonempty array")
if not all(isinstance(value, Mapping) for value in values):
raise ValueError("benchmark dataset entries must be objects")
return values # type: ignore[return-value]