File size: 11,198 Bytes
767a051
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
"""Certified interval crossing, simultaneous event sets, and deterministic ordering."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Iterable, Mapping

from .canonical import attach_hash
from .expression import affine_coefficients, evaluate_dual_interval, evaluate_point
from .interval import ClosedInterval
from .model import IntervalWorld, Relation
from .rational import Q


@dataclass(frozen=True, slots=True)
class CrossingResult:
    relation: Relation
    accepted: bool
    status: str
    exact_root: Q | None
    bracket: ClosedInterval
    certificate: Mapping[str, Any]


def qrecord(value: Q | None) -> dict[str, int] | None:
    return None if value is None else value.to_record()


def state_record(state: Mapping[str, Q]) -> dict[str, dict[str, int]]:
    return {name: value.to_record() for name, value in sorted(state.items())}


def _opposite_sign(a: Q, b: Q) -> bool:
    return a.sign() * b.sign() < 0


def certify_crossing(
    world: IntervalWorld,
    relation: Relation,
    bracket: ClosedInterval,
    *,
    refine_steps: int | None = None,
) -> CrossingResult:
    trajectory = world.trajectory
    active = relation.active_time.intersection(bracket)
    if active is None or active != bracket:
        record = attach_hash({
            "schema": "TOM-CERTIFIED-CROSSING-0.3",
            "world_hash": world.content_hash,
            "relation_id": relation.id,
            "relation_hash": relation.content_hash,
            "status": "outside-active-time",
            "accepted": False,
            "bracket": bracket.to_record(),
        })
        return CrossingResult(relation, False, "outside-active-time", None, bracket, record)

    steps = int(world.solver.get("refine_steps", 24) if refine_steps is None else refine_steps)
    max_steps = int(world.solver.get("max_refine_steps", 128))
    if steps < 0 or steps > max_steps:
        raise ValueError(f"refine_steps {steps} outside 0..{max_steps}")

    f0 = evaluate_point(relation.expression, trajectory, bracket.lower)
    f1 = evaluate_point(relation.expression, trajectory, bracket.upper)
    dual = evaluate_dual_interval(relation.expression, trajectory, bracket)
    affine = affine_coefficients(relation.expression, trajectory)
    exact_root: Q | None = None
    existence = False
    root_kind = "none"

    if f0 == Q(0):
        exact_root = bracket.lower
        existence = True
        root_kind = "left-endpoint"
    elif f1 == Q(0):
        exact_root = bracket.upper
        existence = True
        root_kind = "right-endpoint"
    elif _opposite_sign(f0, f1):
        existence = True
        root_kind = "sign-change"
        if affine is not None and affine[0] != Q(0):
            candidate = -affine[1] / affine[0]
            if bracket.contains(candidate):
                exact_root = candidate
                root_kind = "exact-affine"

    refined = bracket
    rf0, rf1 = f0, f1
    bisections = 0
    if existence and exact_root is None and _opposite_sign(f0, f1):
        lo, hi = bracket.lower, bracket.upper
        flo, fhi = f0, f1
        for _ in range(steps):
            mid = (lo + hi) / 2
            fm = evaluate_point(relation.expression, trajectory, mid)
            bisections += 1
            if fm == Q(0):
                exact_root = mid
                lo = hi = mid
                flo = fhi = fm
                root_kind = "exact-bisection"
                break
            if _opposite_sign(flo, fm):
                hi, fhi = mid, fm
            else:
                lo, flo = mid, fm
        refined = ClosedInterval(lo, hi)
        rf0, rf1 = flo, fhi

    derivative_excludes_zero = dual.derivative.excludes_zero()
    unique = existence and derivative_excludes_zero
    root_state: dict[str, Q] | None = None
    support_ok = False
    compatibility_ok = False
    if exact_root is not None:
        root_state = trajectory.state_at(exact_root)
        support_ok = world.supports[relation.support_id].accepts_point(root_state)
        compatibility_ok = world.compatibilities[relation.compatibility_id].accepts(root_state)
    elif existence:
        root_state_interval = trajectory.state_interval(refined)
        support_ok = world.supports[relation.support_id].contains_interval(root_state_interval)
        # Equality compatibility over a nonpoint bracket is certified only when
        # each constrained field interval is the required point.
        compatibility = world.compatibilities[relation.compatibility_id]
        compatibility_ok = all(
            name in root_state_interval
            and root_state_interval[name].is_point()
            and root_state_interval[name].lower == expected
            for name, expected in compatibility.equals.items()
        )

    accepted = existence and support_ok and compatibility_ok
    if not existence:
        status = "no-certified-crossing"
    elif not support_ok:
        status = "outside-support"
    elif not compatibility_ok:
        status = "incompatible"
    elif exact_root is None:
        status = "accepted-bracket"
    else:
        status = "accepted-exact-root"

    record: dict[str, Any] = {
        "schema": "TOM-CERTIFIED-CROSSING-0.3",
        "world_hash": world.content_hash,
        "trajectory_id": trajectory.id,
        "trajectory_hash": trajectory.content_hash,
        "relation_id": relation.id,
        "relation_hash": relation.content_hash,
        "event_id": relation.event_id,
        "priority": relation.priority,
        "status": status,
        "accepted": accepted,
        "continuity_basis": "finite expression over exact rational affine fields using +,-,*",
        "original_bracket": bracket.to_record(),
        "original_endpoint_residuals": {
            "lower": f0.to_record(),
            "upper": f1.to_record(),
        },
        "original_residual_interval": dual.value.to_record(),
        "derivative_interval": dual.derivative.to_record(),
        "derivative_excludes_zero": derivative_excludes_zero,
        "existence_certified_by_sign_change_or_exact_endpoint": existence,
        "uniqueness_certified_by_monotonic_derivative": unique,
        "root_kind": root_kind,
        "exact_root_time": qrecord(exact_root),
        "refined_bracket": refined.to_record(),
        "refined_endpoint_residuals": {
            "lower": rf0.to_record(),
            "upper": rf1.to_record(),
        },
        "bisections": bisections,
        "support_id": relation.support_id,
        "support_ok": support_ok,
        "compatibility_id": relation.compatibility_id,
        "compatibility_ok": compatibility_ok,
        "root_state": None if root_state is None else state_record(root_state),
        "ordering_key": None if exact_root is None else [
            exact_root.to_record(), relation.priority, relation.id,
            relation.event_id, relation.content_hash,
        ],
    }
    record = attach_hash(record)
    return CrossingResult(relation, accepted, status, exact_root, refined, record)


def certified_events(
    world: IntervalWorld,
    start: Any,
    end: Any,
    *,
    refine_steps: int | None = None,
) -> list[CrossingResult]:
    start_q = Q.from_value(start)
    end_q = Q.from_value(end)
    if end_q < start_q:
        raise ValueError("event interval end is before start")
    if not world.trajectory.domain.contains(start_q) or not world.trajectory.domain.contains(end_q):
        raise ValueError("event interval lies outside trajectory domain")

    results: list[CrossingResult] = []
    seen: set[tuple[str, str]] = set()
    for tick in range(start_q.floor(), end_q.ceil()):
        bracket = ClosedInterval(max(start_q, Q(tick)), min(end_q, Q(tick + 1)))
        if bracket.upper < bracket.lower:
            continue
        for relation in world.relations:
            result = certify_crossing(world, relation, bracket, refine_steps=refine_steps)
            if not result.accepted:
                continue
            root_key = result.exact_root.to_text() if result.exact_root is not None else result.bracket.to_text()
            key = (relation.id, root_key)
            if key in seen:
                continue
            seen.add(key)
            results.append(result)
    results.sort(key=event_order_key)
    return results


def event_order_key(result: CrossingResult) -> tuple[Any, ...]:
    if result.exact_root is None:
        time_key = (1, result.bracket.lower.as_fraction(), result.bracket.upper.as_fraction())
    else:
        time_key = (0, result.exact_root.as_fraction(), result.exact_root.as_fraction())
    relation = result.relation
    return (*time_key, relation.priority, relation.id, relation.event_id, relation.content_hash)


def next_event_set(
    world: IntervalWorld,
    after: Any,
    before: Any,
    *,
    include_after: bool = False,
    refine_steps: int | None = None,
) -> dict[str, Any]:
    after_q = Q.from_value(after)
    results = certified_events(world, after_q, before, refine_steps=refine_steps)
    results = [
        result for result in results
        if result.exact_root is not None
        and (result.exact_root >= after_q if include_after else result.exact_root > after_q)
    ]
    if not results:
        return attach_hash({
            "schema": "TOM-NEXT-EVENT-SET-0.3",
            "world_hash": world.content_hash,
            "after": after_q.to_record(),
            "before": Q.from_value(before).to_record(),
            "status": "none",
            "events": [],
        })
    earliest = results[0].exact_root
    assert earliest is not None
    simultaneous = [result for result in results if result.exact_root == earliest]
    simultaneous.sort(key=event_order_key)
    record = {
        "schema": "TOM-NEXT-EVENT-SET-0.3",
        "world_hash": world.content_hash,
        "trajectory_id": world.trajectory.id,
        "after": after_q.to_record(),
        "before": Q.from_value(before).to_record(),
        "include_after": include_after,
        "status": "accepted",
        "event_time": earliest.to_record(),
        "simultaneity_basis": "exact equal canonical rational root time",
        "event_count": len(simultaneous),
        "event_order": [result.relation.event_id for result in simultaneous],
        "relation_order": [result.relation.id for result in simultaneous],
        "ordering_rule": ["root_time", "priority", "relation_id", "event_id", "relation_hash"],
        "events": [dict(result.certificate) for result in simultaneous],
    }
    return attach_hash(record)


def events_certificate(
    world: IntervalWorld,
    start: Any,
    end: Any,
    *,
    refine_steps: int | None = None,
) -> dict[str, Any]:
    results = certified_events(world, start, end, refine_steps=refine_steps)
    return attach_hash({
        "schema": "TOM-CERTIFIED-EVENTS-IN-SUPPORT-0.3",
        "world_hash": world.content_hash,
        "start": Q.from_value(start).to_record(),
        "end": Q.from_value(end).to_record(),
        "event_count": len(results),
        "ordering_rule": ["root_time", "priority", "relation_id", "event_id", "relation_hash"],
        "events": [dict(result.certificate) for result in results],
    })