File size: 8,915 Bytes
31226fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
"""Bounded action-conditioned multistep transition baseline.

The model learns exact discrete transition contexts ``state + action -> next_state``
and can roll them forward over an explicit action sequence. It is deliberately a
statistical reference model, not a neural world model and not accepted evidence
for planning or counterfactual reasoning.
"""

from __future__ import annotations

import copy
import json
from collections import Counter
from dataclasses import dataclass
from typing import Any, Sequence, cast


class MultistepWorldModelError(ValueError):
    """Raised for malformed transition data or rollout requests."""


def _canonical(value: object) -> str:
    try:
        return json.dumps(
            value,
            sort_keys=True,
            separators=(",", ":"),
            ensure_ascii=True,
            allow_nan=False,
        )
    except (TypeError, ValueError) as error:
        raise MultistepWorldModelError(
            "state and action values must be finite JSON"
        ) from error


def _state(value: object, name: str) -> dict[str, Any]:
    if not isinstance(value, dict):
        raise MultistepWorldModelError(f"{name} must be a string-keyed object")
    raw = cast(dict[object, object], value)
    if not all(isinstance(key, str) for key in raw):
        raise MultistepWorldModelError(f"{name} must be a string-keyed object")
    typed = cast(dict[str, Any], value)
    _canonical(typed)
    return copy.deepcopy(typed)


@dataclass(frozen=True, slots=True)
class StateAction:
    """Explicit action label and JSON payload for one transition step."""

    action: str
    payload: dict[str, Any]

    def __post_init__(self) -> None:
        if not self.action.strip():
            raise MultistepWorldModelError("action must be non-empty")
        _state(self.payload, "action payload")

    def to_dict(self) -> dict[str, Any]:
        return {"action": self.action, "payload": copy.deepcopy(self.payload)}


@dataclass(frozen=True, slots=True)
class RolloutStep:
    step: int
    action: StateAction
    predicted_state: dict[str, Any] | None
    support: int
    uncertainty: float


@dataclass(frozen=True, slots=True)
class RolloutResult:
    initial_state: dict[str, Any]
    steps: tuple[RolloutStep, ...]
    terminated_early: bool

    @property
    def final_state(self) -> dict[str, Any] | None:
        if not self.steps:
            return copy.deepcopy(self.initial_state)
        return copy.deepcopy(self.steps[-1].predicted_state)


class ActionConditionedWorldModel:
    """Bounded exact-context transition counts with deterministic rollouts."""

    model_version = 1

    def __init__(self, *, max_contexts: int = 512) -> None:
        if type(max_contexts) is not int or max_contexts <= 0:
            raise MultistepWorldModelError("max_contexts must be a positive integer")
        self.max_contexts = max_contexts
        self._contexts: dict[str, Counter[str]] = {}

    @staticmethod
    def _context(state: dict[str, Any], action: StateAction) -> str:
        return _canonical({"state": state, "action": action.to_dict()})

    def update(
        self,
        state: dict[str, Any],
        action: StateAction,
        next_state: dict[str, Any],
    ) -> None:
        current = _state(state, "state")
        following = _state(next_state, "next_state")
        key = self._context(current, action)
        label = _canonical(following)
        if key not in self._contexts and len(self._contexts) >= self.max_contexts:
            del self._contexts[next(iter(self._contexts))]
        self._contexts.setdefault(key, Counter[str]())[label] += 1

    def predict(
        self, state: dict[str, Any], action: StateAction
    ) -> tuple[dict[str, Any] | None, int, float]:
        current = _state(state, "state")
        counts = self._contexts.get(self._context(current, action))
        if not counts:
            return None, 0, 1.0
        label = min(counts, key=lambda item: (-counts[item], item))
        support = counts[label]
        total = sum(counts.values())
        return json.loads(label), support, 1.0 - support / total

    def rollout(
        self,
        initial_state: dict[str, Any],
        actions: Sequence[StateAction],
        *,
        max_steps: int = 32,
    ) -> RolloutResult:
        if type(max_steps) is not int or max_steps <= 0:
            raise MultistepWorldModelError("max_steps must be a positive integer")
        if len(actions) > max_steps:
            raise MultistepWorldModelError("action sequence exceeds rollout budget")
        initial = _state(initial_state, "initial_state")
        current = initial
        steps: list[RolloutStep] = []
        terminated = False
        for index, action in enumerate(actions, start=1):
            predicted, support, uncertainty = self.predict(current, action)
            steps.append(RolloutStep(index, action, predicted, support, uncertainty))
            if predicted is None:
                terminated = True
                break
            current = predicted
        return RolloutResult(initial, tuple(steps), terminated)

    def counterfactual(
        self,
        state: dict[str, Any],
        actions: Sequence[StateAction],
    ) -> dict[str, dict[str, Any] | None]:
        """Compare one-step alternatives without claiming causal intervention."""

        result: dict[str, dict[str, Any] | None] = {}
        for action in actions:
            key = _canonical(action.to_dict())
            predicted, _, _ = self.predict(state, action)
            result[key] = predicted
        return result

    def state_dict(self) -> dict[str, Any]:
        return {
            "model_version": self.model_version,
            "max_contexts": self.max_contexts,
            "context_order": list(self._contexts),
            "contexts": {
                key: dict(sorted(values.items()))
                for key, values in self._contexts.items()
            },
            "scientific_status": "statistical_multistep_reference_not_neural_evidence",
        }

    @classmethod
    def from_state_dict(cls, state: dict[str, Any]) -> "ActionConditionedWorldModel":
        if state.get("model_version") != cls.model_version:
            raise MultistepWorldModelError("unsupported multistep world-model version")
        try:
            model = cls(max_contexts=state["max_contexts"])
            raw_contexts = state["contexts"]
            raw_order = state["context_order"]
            if not isinstance(raw_contexts, dict) or not isinstance(raw_order, list):
                raise MultistepWorldModelError("invalid contexts or context order")
            contexts = cast(dict[object, object], raw_contexts)
            order = cast(list[object], raw_order)
            if len(order) != len(contexts) or set(order) != set(contexts):
                raise MultistepWorldModelError("context order does not match contexts")
            if len(order) > model.max_contexts:
                raise MultistepWorldModelError("stored contexts exceed capacity")
            for raw_key in order:
                if not isinstance(raw_key, str):
                    raise MultistepWorldModelError("invalid transition context")
                raw_counts = contexts.get(raw_key)
                if not isinstance(raw_counts, dict):
                    raise MultistepWorldModelError("invalid transition context")
                count_items = cast(dict[object, object], raw_counts)
                counts: Counter[str] = Counter()
                for raw_label, raw_count in count_items.items():
                    if (
                        not isinstance(raw_label, str)
                        or type(raw_count) is not int
                        or raw_count <= 0
                    ):
                        raise MultistepWorldModelError("invalid transition count")
                    label = raw_label
                    count = raw_count
                    decoded = json.loads(label)
                    if _canonical(_state(decoded, "stored next_state")) != label:
                        raise MultistepWorldModelError(
                            "non-canonical stored next_state"
                        )
                    counts[label] = count
                if not counts:
                    raise MultistepWorldModelError(
                        "transition context may not be empty"
                    )
                model._contexts[raw_key] = counts
            return model
        except (KeyError, TypeError, json.JSONDecodeError) as error:
            if isinstance(error, MultistepWorldModelError):
                raise
            raise MultistepWorldModelError(
                "malformed multistep world-model state"
            ) from error


__all__ = [
    "ActionConditionedWorldModel",
    "MultistepWorldModelError",
    "RolloutResult",
    "RolloutStep",
    "StateAction",
]