everydaytok commited on
Commit
346e23e
Β·
verified Β·
1 Parent(s): 1121f09

Update practicality_axioms.py

Browse files
Files changed (1) hide show
  1. practicality_axioms.py +344 -151
practicality_axioms.py CHANGED
@@ -1,9 +1,10 @@
1
  import math
2
  import random
3
  import torch
 
4
  from typing import Dict, List, Tuple, Set, Optional, Any
5
  from dataclasses import dataclass, field
6
- from practicality_core import Problem, DEVICE, SOLVE_THRESHOLD, _c15, IV
7
 
8
  @dataclass
9
  class Hypothesis:
@@ -20,172 +21,364 @@ class Hypothesis:
20
 
21
  @dataclass
22
  class L9Certificate:
23
- residual_ce: float; dominant_vars: List[str]; dominant_exprs: List[str]; tension_class: str="unknown"
24
 
25
  @dataclass
26
  class Baton:
27
- binding: Dict[str, float]; ce: float; ray_id: str=""; depth: int=0; l9: Optional[L9Certificate]=None
28
 
29
  @dataclass
30
  class AxiomRay:
31
- sequence: List[str]; ray_id: str; ray_type: str="seed"
32
- depth: int=0; parent_id: Optional[str]=None
33
- baton: Optional[Baton]=None; ce_prior: float=float('inf')
34
  @property
35
  def name(self) -> str: return "β†’".join(a[:3] for a in self.sequence)
36
  def extend(self, axiom, rtype, new_prior=float('inf')):
37
  return AxiomRay(sequence=self.sequence+[axiom], ray_id=f"{self.ray_id}+{axiom[:3]}",
38
  ray_type=rtype, depth=self.depth+1, parent_id=self.ray_id, ce_prior=new_prior)
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  class Axiom:
41
- CONTINUOUS="CONTINUOUS"; DISCRETE="DISCRETE"; QUADRATIC="QUADRATIC"
42
- BILINEAR="BILINEAR"; METRIC="METRIC"; SYMMETRIC="SYMMETRIC"
43
- MUTABLE="MUTABLE"; EXTREMAL="EXTREMAL"; ENTROPY="ENTROPY"
44
- ATOMIC="ATOMIC"; PARSIMONY="PARSIMONY"; DUALITY="DUALITY"
 
45
 
46
  ALL_AXIOMS = [getattr(Axiom, a) for a in dir(Axiom) if not a.startswith("__")]
47
 
48
- class UCB1BanditSeeder:
49
- def __init__(self):
50
- self.stats = {a: {"tries": 0, "reward": 0.0} for a in ALL_AXIOMS}
51
- self.total_tries = 0
52
-
53
- def record_reward(self, axiom, ce_before, ce_after):
54
- reward = max(0.0, ce_before - ce_after)
55
- self.stats[axiom]["tries"] += 1
56
- self.stats[axiom]["reward"] += reward
57
- self.total_tries += 1
58
-
59
- def intelligent_branch(self, ray, out_baton, remaining_axioms, branch_width):
60
- scored = []
61
- for ax in remaining_axioms:
62
- tries = self.stats[ax]["tries"]
63
- if tries == 0: ucb = 999.0
64
- else:
65
- avg_reward = self.stats[ax]["reward"] / tries
66
- exploration = math.sqrt(math.log(self.total_tries + 1) / tries)
67
- ucb = avg_reward + 0.5 * exploration
68
- scored.append((ucb, ax))
69
- scored.sort(key=lambda x: x[0] * random.random(), reverse=True)
70
- children = []
71
- for _, axiom in scored[:branch_width]:
72
- child = ray.extend(axiom, "branch", new_prior=out_baton.ce)
73
- if child:
74
- child.baton = out_baton
75
- children.append(child)
76
- return children
77
-
78
- def _batched_deduce_and_evaluate(problem: Problem, hyps: List[Hypothesis], steps: int=80) -> List[Tuple[Dict, float, List[str], str]]:
79
- if not hyps: return []
80
-
81
- skip_indices = [i for i, h in enumerate(hyps) if getattr(h, 'is_fully_determined', False) or len(h.free_vars) == 0]
82
- solve_indices = [i for i, h in enumerate(hyps) if i not in skip_indices]
83
- results = [None] * len(hyps)
84
-
85
- for i in skip_indices:
86
- hyp = hyps[i]
87
- try:
88
- ce = problem.scalar_energy(hyp.binding)
89
- dom_vars = list(hyp.pinned_vars.keys())[:3]
90
- results[i] = (hyp.binding, ce, dom_vars, "algebraic")
91
- except: results[i] = (hyp.binding, float('inf'), [], "algebraic_error")
92
-
93
- if not solve_indices: return [r for r in results if r is not None]
94
-
95
- adam_hyps = [hyps[i] for i in solve_indices]
96
- V = len(problem.variables)
97
-
98
- log_mask = []
99
- log_lo, log_hi = [], []
100
- for v in problem.variables:
101
- lo, hi = _c15(problem.bounds[v][0]), _c15(problem.bounds[v][1])
102
- if v in problem.log_space_vars and lo > 0:
103
- log_mask.append(True)
104
- log_lo.append(math.log10(max(lo, 1e-30)))
105
- log_hi.append(math.log10(max(hi, 1e-30)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  else:
107
- log_mask.append(False)
108
- log_lo.append(lo)
109
- log_hi.append(hi)
110
-
111
- log_mask_t = torch.tensor(log_mask, device=DEVICE, dtype=torch.bool)
112
- lo_param_t = torch.tensor(log_lo, device=DEVICE, dtype=torch.float32)
113
- hi_param_t = torch.tensor(log_hi, device=DEVICE, dtype=torch.float32)
114
-
115
- def _param_to_orig(P):
116
- orig = P.clone()
117
- if log_mask_t.any(): orig[:, log_mask_t] = torch.pow(10.0, P[:, log_mask_t])
118
- return orig
119
-
120
- def _orig_to_param(x_val, j):
121
- if log_mask[j] and x_val > 0: return math.log10(max(x_val, 1e-30))
122
- return x_val
123
-
124
- x_data_p, mask_data, target_data_p = [], [], []
125
- for hyp in adam_hyps:
126
- xr, mr, tr = [], [], []
127
- active_vars = problem.get_markov_blanket(set(hyp.pinned_vars.keys()), depth=2)
128
- for j, v in enumerate(problem.variables):
129
- lo, hi = _c15(problem.bounds[v][0]), _c15(problem.bounds[v][1])
130
- if v in hyp.pinned_vars:
131
- p_val = _orig_to_param(_c15(hyp.pinned_vars[v]), j)
132
- xr.append(p_val); mr.append(0.0); tr.append(p_val)
133
- else:
134
- p_val = _orig_to_param(_c15(hyp.binding.get(v, (lo+hi)/2)), j)
135
- is_active = (v in active_vars) or (len(hyp.pinned_vars) == 0)
136
- xr.append(p_val); mr.append(1.0 if is_active else 0.0); tr.append(0.0)
137
- x_data_p.append(xr); mask_data.append(mr); target_data_p.append(tr)
138
-
139
- P = torch.tensor(x_data_p, device=DEVICE, dtype=torch.float32, requires_grad=True)
140
- mask = torch.tensor(mask_data, device=DEVICE, dtype=torch.float32)
141
- target = torch.tensor(target_data_p, device=DEVICE, dtype=torch.float32)
142
-
143
- optimizer = torch.optim.Adam([P], lr=0.01)
144
-
145
- for step in range(steps):
146
- optimizer.zero_grad()
147
- step_ratio = min(1.0, step / (steps * 0.8))
148
- X_orig = _param_to_orig(P)
149
- ce = problem.tensor_energy(X_orig, step_ratio, is_optimizing=True)
150
- if isinstance(ce, torch.Tensor) and (ce < SOLVE_THRESHOLD).all() and step_ratio == 1.0: break
151
- ce.sum().backward()
152
- with torch.no_grad():
153
- P.grad.clamp_(-10.0, 10.0)
154
- P.grad *= mask
155
- optimizer.step()
156
- P.data = torch.where(mask == 0.0, target, P.data)
157
- margin = 0.1 * (1.0 - step_ratio)
158
- lo_m = lo_param_t - (hi_param_t - lo_param_t) * margin
159
- hi_m = hi_param_t + (hi_param_t - lo_param_t) * margin
160
- P.data = torch.clamp(P.data, lo_m.unsqueeze(0), hi_m.unsqueeze(0))
161
-
162
- X_orig_final = _param_to_orig(P)
163
- final_ce = problem.tensor_energy(X_orig_final, 1.0, is_optimizing=False).view(-1)
164
- ce_vals = final_ce.detach().cpu().numpy()
165
- X_vals = X_orig_final.detach().cpu().numpy()
166
-
167
- for b_idx, orig_idx in enumerate(solve_indices):
168
- final_b = {problem.variables[j]: float(X_vals[b_idx, j]) for j in range(V)}
169
- results[orig_idx] = (final_b, float(ce_vals[b_idx]), [], "systemic")
170
-
171
- return [r for r in results if r is not None]
172
-
173
- def _mprt_sample(problem: Problem, N: int):
174
- var_list = problem.variables; V = len(var_list)
175
- lo_t = torch.tensor([_c15(problem.bounds.get(v, (-10.0, 10.0))[0]) for v in var_list], device=DEVICE, dtype=torch.float32)
176
- hi_t = torch.tensor([_c15(problem.bounds.get(v, (-10.0, 10.0))[1]) for v in var_list], device=DEVICE, dtype=torch.float32)
177
- for i in range(V):
178
- if lo_t[i] >= hi_t[i]: m = (lo_t[i]+hi_t[i])/2; lo_t[i] = m - 1e-6; hi_t[i] = m + 1e-6
179
 
180
- rand_base = torch.rand((N, V), device=DEVICE)
181
- lsv_indices = [problem.var_idx[v] for v in problem.log_space_vars if v in problem.var_idx]
182
- for idx in lsv_indices:
183
- lo_v, hi_v = lo_t[idx].item(), hi_t[idx].item()
184
- if lo_v > 0 and hi_v > lo_v:
185
- log_lo, log_hi = math.log10(max(lo_v, 1e-30)), math.log10(max(hi_v, 1e-30))
186
- rand_base[:, idx] = torch.pow(10.0, torch.rand(N, device=DEVICE)*(log_hi-log_lo)+log_lo) / hi_v
 
 
 
 
187
 
188
- X = lo_t.unsqueeze(0) + (hi_t - lo_t).unsqueeze(0) * rand_base
189
- ce_batch = problem.tensor_energy(X, 1.0, is_optimizing=False).view(-1)
190
- best_idx = torch.argmin(ce_batch).item()
191
- return {v: float(X[best_idx, i].item()) for i, v in enumerate(var_list)}, ce_batch[best_idx].item()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import math
2
  import random
3
  import torch
4
+ import itertools
5
  from typing import Dict, List, Tuple, Set, Optional, Any
6
  from dataclasses import dataclass, field
7
+ import practicality_core as core
8
 
9
  @dataclass
10
  class Hypothesis:
 
21
 
22
  @dataclass
23
  class L9Certificate:
24
+ residual_ce: float; dominant_vars: List[str]; dominant_exprs: List[str]; tension_class: str = "unknown"
25
 
26
  @dataclass
27
  class Baton:
28
+ binding: Dict[str, float]; ce: float; ray_id: str = ""; depth: int = 0; l9: Optional[L9Certificate] = None
29
 
30
  @dataclass
31
  class AxiomRay:
32
+ sequence: List[str]; ray_id: str; ray_type: str = "seed"
33
+ depth: int = 0; parent_id: Optional[str] = None
34
+ baton: Optional[Baton] = None; ce_prior: float = float('inf')
35
  @property
36
  def name(self) -> str: return "β†’".join(a[:3] for a in self.sequence)
37
  def extend(self, axiom, rtype, new_prior=float('inf')):
38
  return AxiomRay(sequence=self.sequence+[axiom], ray_id=f"{self.ray_id}+{axiom[:3]}",
39
  ray_type=rtype, depth=self.depth+1, parent_id=self.ray_id, ce_prior=new_prior)
40
 
41
+ @dataclass
42
+ class HypothesisTrace:
43
+ hid: str; round_idx: int; ray_name: str; ray_type: str
44
+ ce_before: float; ce_after: float; survived: bool; elapsed_ms: float
45
+ g1_pass: bool = False; g2_pass: bool = False
46
+ binding: Dict[str, float] = field(default_factory=dict)
47
+ invariant_results: Dict[str, Tuple[float, bool]] = field(default_factory=dict)
48
+ tension_class: str = "unknown"; depth: int = 0
49
+
50
+ @dataclass
51
+ class StructuralModel:
52
+ best_binding: Dict[str, float]; best_ce: float; best_ray: str = "β€”"
53
+ failure_patterns: Set[str] = field(default_factory=set)
54
+ resonant_pairs: List[Tuple[str, str]] = field(default_factory=list)
55
+
56
  class Axiom:
57
+ CONTINUOUS = "CONTINUOUS"; DISCRETE = "DISCRETE"; QUADRATIC = "QUADRATIC"
58
+ BILINEAR = "BILINEAR"; METRIC = "METRIC"; SYMMETRIC = "SYMMETRIC"
59
+ MUTABLE = "MUTABLE"; EXTREMAL = "EXTREMAL"; ENTROPY = "ENTROPY"
60
+ ATOMIC = "ATOMIC"; PARSIMONY = "PARSIMONY"; DUALITY = "DUALITY"
61
+ MONOTONE_PRODUCT = "MONOTONE_PRODUCT"; ORDERED = "ORDERED"
62
 
63
  ALL_AXIOMS = [getattr(Axiom, a) for a in dir(Axiom) if not a.startswith("__")]
64
 
65
+ # ══════════════════════════════════════════════════════════════════════
66
+ # AXIOM HYPOTHESIS CONSTRUCTORS
67
+ # ══════════════════════════════════════════════════════════════════════
68
+ def _make_hyp(hid, binding, h_type, claim, derivation, pinned, free, conf, is_fd=False):
69
+ return Hypothesis(hid=hid, binding=binding, h_type=h_type, claim=claim,
70
+ derivation=derivation, pinned_vars=pinned, free_vars=free,
71
+ confidence=conf, is_fully_determined=is_fd)
72
+
73
+ def _hyp_continuous(p, bt, a, m): return [_make_hyp("cnt", bt.binding, "continuous", "Manifold", [], {}, p.variables, 0.45)]
74
+ def _hyp_discrete(p, bt, a, m):
75
+ b = dict(bt.binding); pinned = {}
76
+ box = {v: core.IV(b.get(v, 0)-max(0.05*(p.bounds[v][1]-p.bounds[v][0]), 1e-4),
77
+ b.get(v, 0)+max(0.05*(p.bounds[v][1]-p.bounds[v][0]), 1e-4)) for v in p.variables if v in p.bounds}
78
+ cont = core._hc4(box, p.compiled_constraints)
79
+ if cont:
80
+ for v, iv in cont.items():
81
+ b[v] = iv.mid()
82
+ if iv.width() < 1e-2: pinned[v] = b[v]
83
+ return [_make_hyp("dis", b, "discrete", "TopoScope", [], pinned, p.variables, 0.80)]
84
+ def _hyp_quadratic(p, bt, a, m):
85
+ hyps = []; b = dict(bt.binding)
86
+ if bt.l9 and bt.l9.dominant_vars:
87
+ for v in bt.l9.dominant_vars[:2]: hyps.append(_make_hyp(f"qua_{v}", b, "quadratic", f"Root({v})", [], {v: b[v]}, p.variables, 0.70))
88
+ return hyps if hyps else [_make_hyp("qua", b, "quadratic", "Root", [], {}, p.variables, 0.55)]
89
+ def _hyp_bilinear(p, bt, a, m):
90
+ hyps = []; b = dict(bt.binding)
91
+ for vi, vj in p.bilinear_pairs:
92
+ lo_i, hi_i = p.bounds.get(vi, (0, 10)); lo_j, hi_j = p.bounds.get(vj, (0, 10))
93
+ product = abs(b.get(vi, 1)*b.get(vj, 1))
94
+ if product <= 0: continue
95
+ gm = math.sqrt(product)
96
+ if lo_i <= gm <= hi_i and lo_j <= gm <= hi_j:
97
+ nb = dict(b); nb[vi] = nb[vj] = gm
98
+ hyps.append(_make_hyp(f"bil_eq_{vi}_{vj}", nb, "bilinear", "GeoMean", ["bilinear"], {vi: gm, vj: gm}, [v for v in p.variables if v not in [vi, vj]], 0.70))
99
+ return hyps
100
+ def _hyp_monotone_product(p, bt, a, m):
101
+ hyps = []; b = dict(bt.binding)
102
+ if not p.monotone_targets: return hyps
103
+ try: tb = core._global_hc4_tighten_bounds(p)
104
+ except: tb = dict(p.bounds)
105
+ for v_small, v_large, k in p.monotone_targets:
106
+ lo_s, hi_s = p.bounds.get(v_small, (-1e9, 1e9)); lo_l, hi_l = p.bounds.get(v_large, (-1e9, 1e9))
107
+ for ratio in [0.25, 0.5, 0.707, 0.9]:
108
+ vs_sq = k*ratio
109
+ if vs_sq <= 0: continue
110
+ vs = math.sqrt(vs_sq); vl = k/vs
111
+ if not(lo_s <= vs <= hi_s and lo_l <= vl <= hi_l and vs <= vl): continue
112
+ nb = dict(b); nb[v_small] = vs; nb[v_large] = vl
113
+ box = {u: core.IV(*tb.get(u, p.bounds.get(u, (-10, 10)))) for u in p.variables}
114
+ box[v_small] = core.IV(vs-1e-6, vs+1e-6); box[v_large] = core.IV(vl-1e-6, vl+1e-6)
115
+ cont = core._hc4(box, p.compiled_constraints); pinned = {v_small: vs, v_large: vl}
116
+ if cont:
117
+ for u, iv in cont.items():
118
+ if u in p.bounds: nb[u] = iv.mid()
119
+ pinned = {u: nb[u] for u in p.variables if cont[u].width() < 1e-2}
120
+ hyps.append(_make_hyp(f"mpr_{v_small}_{v_large}_r{int(ratio*100)}", nb, "monotone_product",
121
+ "MonoProd", ["ordered", "hc4"], pinned, [u for u in p.variables if u not in pinned], 0.88))
122
+ return hyps
123
+ def _hyp_metric(p, bt, a, m):
124
+ hyps = []; b = dict(bt.binding)
125
+ for mc in p.compiled_constraints:
126
+ if mc.kind != "equality" or not mc.parsed or not mc.parsed.is_Add: continue
127
+ sq_terms = [t for t in mc.parsed.args if t.is_Pow and t.args[1] == 2]
128
+ if len(sq_terms) < 1: continue
129
+ vars_in = [str(s) for s in mc.parsed.free_symbols if str(s) in p.variables]
130
+ if not vars_in: continue
131
+ const_terms = [float(t) for t in mc.parsed.args if t.is_Number]
132
+ if not const_terms: continue
133
+ target = -const_terms[0]
134
+ if target <= 0: continue
135
+ curr_val = sum(b.get(v, 0)**2 for v in vars_in)
136
+ if curr_val < 1e-8: continue
137
+ scale = math.sqrt(target/curr_val); nb = dict(b)
138
+ for v in vars_in:
139
+ lo, hi = p.bounds.get(v, (-10, 10)); nb[v] = max(lo, min(hi, b.get(v, 0)*scale))
140
+ hyps.append(_make_hyp(f"met_{hash(mc.expr_str)%9999}", nb, "metric", "RadialProject", ["metric", mc.expr_str], {}, list(p.variables), 0.75))
141
+ return hyps
142
+ def _hyp_symmetric(p, bt, a, m):
143
+ hyps = []; b = dict(bt.binding)
144
+ vl = p.variables
145
+ for i in range(min(len(vl), 8)):
146
+ for j in range(i+1, min(len(vl), 8)):
147
+ vi, vj = vl[i], vl[j]
148
+ lo_i, hi_i = p.bounds.get(vi, (-1e9, 1e9)); lo_j, hi_j = p.bounds.get(vj, (-1e9, 1e9))
149
+ bvi, bvj = b.get(vi, 0), b.get(vj, 0)
150
+ if lo_i <= bvj <= hi_i and lo_j <= bvi <= hi_j:
151
+ nb = dict(b); nb[vi], nb[vj] = bvj, bvi
152
+ hyps.append(_make_hyp(f"sym_{vi}_{vj}", nb, "symmetric", "Swap", ["symmetric"], {vi: nb[vi], vj: nb[vj]}, [v for v in p.variables if v not in [vi, vj]], 0.60))
153
+ return hyps[:5]
154
+ def _hyp_ordered(p, bt, a, m): return [_make_hyp("ord", bt.binding, "ordered", "OrderBal", [], {}, p.variables, 0.67)]
155
+ def _hyp_monotone(p, bt, a, m): return [_make_hyp("mon", bt.binding, "monotone", "MonoPath", [], {}, p.variables, 0.67)]
156
+ def _hyp_mutable(p, bt, a, m):
157
+ b = dict(bt.binding); pinned = {}
158
+ if bt.l9 and bt.l9.dominant_vars:
159
+ v = bt.l9.dominant_vars[0]; lo, hi = p.bounds.get(v, (-10, 10))
160
+ b[v] = max(lo, min(hi, b.get(v, 0)+random.gauss(0, (hi-lo)*0.05))); pinned = {v: b[v]}
161
+ return [_make_hyp("mut", b, "mutable", "Perturb", [], pinned, p.variables, 0.40)]
162
+ def _hyp_extremal(p, bt, a, m):
163
+ hyps = []; b = dict(bt.binding)
164
+ if bt.l9 and bt.l9.dominant_vars:
165
+ v = bt.l9.dominant_vars[0]; lo, hi = p.bounds.get(v, (0, 1))
166
+ for val in [lo, hi]: nb = dict(b); nb[v] = val; hyps.append(_make_hyp(f"ext_{v}_{val:.3f}", nb, "extremal", "PinBound", [], {v: val}, p.variables, 0.65))
167
+ return hyps
168
+ def _hyp_entropy(p, bt, a, m): return [_make_hyp("ent", {v: random.uniform(*p.bounds[v]) for v in p.variables}, "entropy", "MaxEnt", [], {}, p.variables, 0.48)]
169
+ def _hyp_atomic(p, bt, a, m):
170
+ hyps = []; b = dict(bt.binding)
171
+ for mc in p.compiled_constraints:
172
+ if not mc.projections: continue
173
+ for v, projs in mc.projections.items():
174
+ for proj in projs:
175
+ try:
176
+ val = float(proj["func"](*[b.get(s, 0.0) for s in proj["syms"]])); lo, hi = p.bounds.get(v, (-1e9, 1e9))
177
+ if math.isfinite(val) and lo <= val <= hi:
178
+ nb = dict(b); nb[v] = val; hyps.append(_make_hyp(f"ato_{v}", nb, "atomic", f"Solve({v})", [], {v: val}, p.variables, 0.70)); break
179
+ except: pass
180
+ if hyps: break
181
+ return hyps
182
+ def _hyp_parsimony(p, bt, a, m):
183
+ b = dict(bt.binding); nb = {}
184
+ max_val = max((abs(v) for v in b.values() if math.isfinite(v)), default=1.0)
185
+ for v in p.variables:
186
+ val = b[v]; lo, hi = p.bounds[v]
187
+ if not math.isfinite(val): nb[v] = 0.0; continue
188
+ if abs(val) < 0.1 * max_val and lo <= 0.0 <= hi: nb[v] = 0.0
189
  else:
190
+ candidates = [round(val*m)/m for m in [1, 2, 4] if lo <= round(val*m)/m <= hi]
191
+ nb[v] = min(candidates, key=lambda c: abs(c-val)) if candidates else val
192
+ return [_make_hyp("par", nb, "parsimony", "Occam", [], {}, p.variables, 0.58)]
193
+ def _hyp_duality(p, bt, a, m): return [_make_hyp("dua", bt.binding, "duality", "Tight", [], {}, p.variables, 0.68)]
194
+
195
+ AXIOM_CONSTRUCTORS = {
196
+ Axiom.CONTINUOUS: _hyp_continuous, Axiom.DISCRETE: _hyp_discrete,
197
+ Axiom.QUADRATIC: _hyp_quadratic, Axiom.BILINEAR: _hyp_bilinear,
198
+ Axiom.METRIC: _hyp_metric, Axiom.SYMMETRIC: _hyp_symmetric,
199
+ Axiom.ORDERED: _hyp_ordered, Axiom.MONOTONE: _hyp_monotone,
200
+ Axiom.MUTABLE: _hyp_mutable, Axiom.EXTREMAL: _hyp_extremal,
201
+ Axiom.ENTROPY: _hyp_entropy, Axiom.ATOMIC: _hyp_atomic,
202
+ Axiom.PARSIMONY: _hyp_parsimony, Axiom.DUALITY: _hyp_duality,
203
+ Axiom.MONOTONE_PRODUCT: _hyp_monotone_product
204
+ }
205
+
206
+ # ══════════════════════════════════════════════════════════════════════
207
+ # SEED & TEMPLATE SYSTEM
208
+ # ══════════════════════════════════════════════════════════════════════
209
+ def compute_sketch(problem: core.Problem) -> Dict[str, Any]:
210
+ notes = []
211
+ affinities = {a: 1.0 for a in ALL_AXIOMS}
212
+
213
+ if problem.bilinear_pairs:
214
+ affinities[Axiom.BILINEAR] += 4.0
215
+ notes.append(f"Found bilinear pairs: {problem.bilinear_pairs}")
216
+ if problem.monotone_targets:
217
+ affinities[Axiom.MONOTONE_PRODUCT] += 5.0
218
+ notes.append("Boosted Monotone Product based on algebraic layout.")
219
+ if any(mc.kind == "equality" and mc.parsed is not None and mc.parsed.is_Add for mc in problem.compiled_constraints):
220
+ affinities[Axiom.METRIC] += 3.0
221
+ notes.append("System contains sum-of-squares style constraints.")
222
+
223
+ return {"notes": notes, "affinities": affinities}
224
+
225
+ def generate_templates(problem: core.Problem, sketch: Dict[str, Any]) -> List[Hypothesis]:
226
+ templates = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ # 1. INT Grid Sweep & Algebraic Propagation
229
+ tight_int_vars = [v for v in problem.variables if v in problem.int_vars
230
+ and math.isfinite(problem.bounds[v][0])
231
+ and problem.bounds[v][1] - problem.bounds[v][0] <= 100]
232
+
233
+ if tight_int_vars:
234
+ grids = []
235
+ for v in tight_int_vars:
236
+ lo = int(math.ceil(problem.bounds[v][0]))
237
+ hi = int(math.floor(problem.bounds[v][1]))
238
+ grids.append([(v, float(val)) for val in range(lo, hi+1)])
239
 
240
+ combos = list(itertools.product(*grids))[:1000] # Cap search width
241
+ for combo in combos:
242
+ pinned = dict(combo)
243
+ resolved, log = core.algebraic_propagate_pinned(problem, pinned)
244
+ templates.append(
245
+ Hypothesis(
246
+ hid=f"grid_{hash(str(combo))%100000}", binding=resolved,
247
+ h_type="int_grid", claim="Integer Grid Sweep", derivation=log,
248
+ pinned_vars=pinned, free_vars=[v for v in problem.variables if v not in resolved],
249
+ confidence=0.95, is_fully_determined=(len(resolved) == len(problem.variables))
250
+ )
251
+ )
252
+
253
+ # 2. Transcendental Space Sweep (Fallback)
254
+ trans_vars = [v for v in problem.variables if v not in tight_int_vars and math.isfinite(problem.bounds[v][0])]
255
+ if trans_vars and len(trans_vars) <= 2:
256
+ for v in trans_vars:
257
+ lo, hi = problem.bounds[v]
258
+ samples = [lo + (hi-lo)*r for r in [0.15, 0.5, 0.85]]
259
+ for s in samples:
260
+ pinned = {v: s}
261
+ resolved, log = core.algebraic_propagate_pinned(problem, pinned)
262
+ templates.append(
263
+ Hypothesis(
264
+ hid=f"trans_{v}_{int(s*100)%1000}", binding=resolved,
265
+ h_type="grid_sweep", claim="Transcendental Sweep", derivation=log,
266
+ pinned_vars=pinned, free_vars=[u for u in problem.variables if u not in resolved],
267
+ confidence=0.75
268
+ )
269
+ )
270
+ return templates
271
+
272
+ # ══════════════════════════════════════════════════════════════════════
273
+ # THE DECOUPLED SEQUENCE RAY TRACER
274
+ # ══════════════════════════════════════════════════════════════════════
275
+ class SequenceRayTracer:
276
+ def __init__(self, log_callback: Callable, update_state_callback: Callable):
277
+ self.log = log_callback
278
+ self.update_state = update_state_callback
279
+ self.bandit = UCB1BanditSeeder()
280
+ self.baton_registry = {}
281
+
282
+ def trace(self, problem: core.Problem, init_b: Dict[str, float], init_ce: float, templates: List[Hypothesis], max_rays: int = 1500) -> Tuple[Dict[str, float], float, List[HypothesisTrace], str]:
283
+ self.log(f"Initializing Axiom Ray Tracer on {problem.pid}...")
284
+ sketch = compute_sketch(problem)
285
+ for note in sketch["notes"]:
286
+ self.log(f" [Sketch] {note}")
287
+
288
+ best_ce = init_ce
289
+ best_binding = dict(init_b)
290
+ best_ray_name = "Zero-Shot"
291
+ traces = []
292
+ solved = False
293
+
294
+ # Build initial queue from verified templates
295
+ queues = {"core": []}
296
+ for idx, t in enumerate(templates):
297
+ binding = dict(init_b); binding.update(t.pinned_vars)
298
+ results = _batched_deduce_and_evaluate(problem, [t], steps=60)
299
+ if results:
300
+ final_b, final_ce, dom_vars, tc = results[0]
301
+ if final_ce < best_ce:
302
+ best_ce = final_ce
303
+ best_binding = dict(final_b)
304
+ best_ray_name = f"template:{t.h_type}"
305
+
306
+ traces.append(HypothesisTrace(
307
+ hid=t.hid, round_idx=idx, ray_name=f"template:{t.h_type}", ray_type="template",
308
+ ce_before=init_ce, ce_after=final_ce, survived=True, elapsed_ms=0.0,
309
+ g1_pass=(final_ce < core.SOLVE_THRESHOLD), binding=final_b, tension_class=tc
310
+ ))
311
+
312
+ # Register batons for structural branch mapping
313
+ self.baton_registry[t.hid] = Baton(
314
+ binding=final_b, ce=final_ce, ray_id=t.hid,
315
+ l9=L9Certificate(final_ce, dom_vars, [], tc)
316
+ )
317
+
318
+ # Build seed rays based on UCB1 Bandit weights
319
+ affinities = sketch["affinities"]
320
+ seeds_scored = sorted([(affinities.get(a, 1.0), idx, a) for idx, a in enumerate(ALL_AXIOMS)], key=lambda x: -x[0])
321
+ seed_rays = [AxiomRay([a], f"S{idx}", "seed", ce_prior=best_ce) for _, idx, a in seeds_scored[:150]]
322
+ queues["core"].extend(seed_rays)
323
+
324
+ model = StructuralModel(best_binding, best_ce)
325
+ total_fired = 0
326
+ seed_baton = Baton(binding=best_binding, ce=best_ce, ray_id="SEED")
327
+
328
+ while total_fired < max_rays and queues["core"]:
329
+ batch_rays = queues["core"][:64]
330
+ queues["core"] = queues["core"][64:]
331
+ total_fired += len(batch_rays)
332
+
333
+ batch_hyps = []
334
+ for idx, ray in enumerate(batch_rays):
335
+ p_baton = ray.baton or seed_baton
336
+ constructor = AXIOM_CONSTRUCTORS.get(ray.sequence[-1])
337
+ constructed_hyps = constructor(problem, p_baton, list(self.baton_registry.values()), model) if constructor else []
338
+ if not constructed_hyps:
339
+ constructed_hyps = [_make_hyp("fb", p_baton.binding, "fallback", "Pass", [], {}, problem.variables, 0.1)]
340
+ batch_hyps.append((idx, constructed_hyps[0]))
341
+
342
+ eval_results = _batched_deduce_and_evaluate(problem, [h for _, h in batch_hyps], steps=80)
343
+
344
+ for (ray_idx, hyp), (final_b, final_ce, dom_vars, tension_class) in zip(batch_hyps, eval_results):
345
+ ray = batch_rays[ray_idx]
346
+ parent_ce = (ray.baton or seed_baton).ce
347
+ self.bandit.record_reward(ray.sequence[-1], parent_ce, final_ce)
348
+
349
+ improved = final_ce < best_ce
350
+ passed_pruning = final_ce < parent_ce * 0.98
351
+
352
+ if improved:
353
+ best_ce = final_ce
354
+ best_binding = dict(final_b)
355
+ best_ray_name = ray.name
356
+ model.best_binding = dict(final_b)
357
+ model.best_ce = final_ce
358
+ model.best_ray = ray.name
359
+ self.update_state(best_ce=best_ce, total_fired=total_fired, best_ray=best_ray_name)
360
+
361
+ if final_ce < core.SOLVE_THRESHOLD:
362
+ solved = True
363
+
364
+ out_baton = Baton(
365
+ binding=final_b, ce=final_ce, ray_id=ray.ray_id,
366
+ l9=L9Certificate(final_ce, dom_vars, [], tension_class)
367
+ )
368
+
369
+ if final_ce < max(2.0, best_ce * 1.2):
370
+ self.baton_registry[ray.ray_id] = out_baton
371
+
372
+ if (passed_pruning or improved or ray.depth < 1) and ray.depth < 8:
373
+ remaining_axioms = [a for a in ALL_AXIOMS if a not in ray.sequence]
374
+ queues["core"].extend(self.bandit.intelligent_branch(ray, out_baton, remaining_axioms, branch_width=6))
375
+
376
+ if solved:
377
+ self.log("Exact structural resonance found!")
378
+ break
379
+
380
+ return best_binding, best_ce, traces, best_ray_name
381
+
382
+ # Re-expose batch optimization so axioms engine remains self-contained
383
+ _batched_deduce_and_evaluate = axioms._batched_deduce_and_evaluate
384
+ _mprt_sample = axioms._mprt_sample