Skip to content

Commit 3ebf9e3

Browse files
authored
Merge pull request #25 from tylerbessire/V2
V2
2 parents 40104f3 + 9da7035 commit 3ebf9e3

File tree

13 files changed

+1549
-0
lines changed

13 files changed

+1549
-0
lines changed

.github/pull_request_template.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Summary
2+
- [ ] What changed?
3+
- [ ] Why?
4+
5+
## Testing
6+
- [ ] Steps
7+
- [ ] Results
8+
9+
## Risk Assessment
10+
- [ ] Regressions considered
11+
- [ ] Rollback plan
12+
13+
[S:PR v1] template=installed pass

arc_solver/solver.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
synthesize as synth_baseline,
2020
predict_two as predict_two_baseline,
2121
)
22+
from puma.rft.feature_flags import RFT_ENABLED, build_limits
23+
from puma.rft.orchestrator import solve_with_rft
24+
from puma.rft.tracking import HypothesisMemory
25+
from puma.rft.demo.grid import build_context as build_rft_grid_context
26+
from puma.rft.explain import explain_last_run
2227
from .enhanced_search import synthesize_with_enhancements, predict_two_enhanced
2328
from .hypothesis import HypothesisEngine, Hypothesis
2429
from .continuous_learning import ContinuousSelfMemory
@@ -79,6 +84,10 @@ def __init__(self, use_enhancements: bool = True,
7984
self._placeholder_templates: List[PlaceholderTemplate] = []
8085
self._new_placeholder_templates: List[PlaceholderTemplate] = []
8186
self._last_hypotheses: List[Hypothesis] = []
87+
self.rft_enabled = RFT_ENABLED
88+
self._rft_limits = build_limits()
89+
self._rft_memory = HypothesisMemory()
90+
self._last_rft_status: Optional[str] = None
8291

8392
def solve_task(self, task: Dict[str, List[Dict[str, List[List[int]]]]]) -> Dict[str, List[List[List[int]]]]:
8493
"""Solve a single ARC task using enhanced or baseline methods."""
@@ -177,6 +186,9 @@ def solve_task(self, task: Dict[str, List[Dict[str, List[List[int]]]]]) -> Dict[
177186
self._record_continuous_experience(task_id, train_pairs, best_hypothesis, solved_training, result)
178187
if solved_training:
179188
self.stats['tasks_solved'] += 1
189+
if self.rft_enabled:
190+
attempt1, attempt2 = self._maybe_run_rft_adapter(task, attempt1, attempt2)
191+
result = {"attempt_1": attempt1, "attempt_2": attempt2}
180192
return result
181193

182194
def _get_predictions(
@@ -216,6 +228,50 @@ def _get_predictions(
216228
self.logger.info("Using baseline prediction")
217229
return baseline
218230

231+
def _maybe_run_rft_adapter(
232+
self,
233+
task: Dict[str, Any],
234+
attempt1: List[List[List[int]]],
235+
attempt2: List[List[List[int]]],
236+
) -> Tuple[List[List[List[int]]], List[List[List[int]]]]:
237+
"""Optionally run the RFT orchestrator when the demo flag is present."""
238+
239+
metadata = task.get("metadata") or {}
240+
rft_config = metadata.get("rft_demo")
241+
if not rft_config or "grid" not in rft_config:
242+
return attempt1, attempt2
243+
grid_spec = rft_config["grid"]
244+
grid = grid_spec.get("grid")
245+
targets = grid_spec.get("targets", [])
246+
if not isinstance(grid, list) or not grid:
247+
return attempt1, attempt2
248+
try:
249+
context, rules = build_rft_grid_context(grid, targets, limits=self._rft_limits)
250+
except Exception:
251+
return attempt1, attempt2
252+
context, status = solve_with_rft(
253+
context,
254+
rules,
255+
self._rft_memory,
256+
outer_budget=self._rft_limits.outer_budget,
257+
)
258+
self._last_rft_status = status
259+
if self.enable_logging:
260+
self.logger.debug("RFT status=%s\n%s", status, explain_last_run(context))
261+
if status != "DONE" or not grid_spec.get("inject_output", False):
262+
return attempt1, attempt2
263+
solved_grid = context.state.grid
264+
if solved_grid and isinstance(solved_grid[0][0], str):
265+
palette: Dict[str, int] = {"black": 0}
266+
next_id = 1
267+
for row in solved_grid:
268+
for color in row:
269+
if color not in palette:
270+
palette[color] = next_id
271+
next_id += 1
272+
solved_grid = [[palette[color] for color in row] for row in solved_grid]
273+
return [solved_grid for _ in attempt1], [solved_grid for _ in attempt2]
274+
219275
def _postprocess_predictions(
220276
self,
221277
train_pairs: List[Tuple[Array, Array]],

puma/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""PUMA meta-learning components."""
2+
from importlib import import_module
3+
4+
__all__ = ["rft"]
5+
6+
7+
def __getattr__(name):
8+
if name == "rft":
9+
return import_module("puma.rft")
10+
raise AttributeError(name)

puma/rft/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# PUMA RFT Module
2+
3+
[S:DOC v2] scope=puma.rft overview=complete pass
4+
5+
The Relational Frame Theory (RFT) module adds pliance-based rule following and
6+
tracking-driven rule variation to the PUMA solver while remaining fully
7+
feature-flagged behind `PUMA_RFT`.
8+
9+
## Architecture
10+
11+
```
12+
+-------------------+ +----------------------+ +------------------+
13+
| feature_flags | ---> | pliance (determinism)| ---> | explain |
14+
+-------------------+ +----------------------+ +------------------+
15+
| | ^
16+
v v |
17+
+-------------------+ +----------------------+ +------------------+
18+
| demo/grid harness | ---> | tracking (variation) | ---> | orchestrator |
19+
+-------------------+ +----------------------+ +------------------+
20+
```
21+
22+
* `types.py` exposes typed primitives (rules, context, hypotheses).
23+
* `pliance.py` executes prioritized rules deterministically.
24+
* `tracking.py` evaluates adaptive hypotheses and promotes successful ones.
25+
* `orchestrator.py` alternates pliance and tracking with metrics and guards.
26+
* `explain.py` records rich traces surfaced via `explain_last_run()`.
27+
28+
## Usage
29+
30+
1. Enable the feature flag: `export PUMA_RFT=1`.
31+
2. Run the demo tests: `pytest tests/rft/test_rft_integration.py -q`.
32+
3. Inspect the most recent trace:
33+
34+
```python
35+
from puma.rft.explain import explain_last_run
36+
print(explain_last_run())
37+
```
38+
39+
To exercise the adapter inside the ARC solver:
40+
41+
```bash
42+
PUMA_RFT=1 python -c 'from arc_solver import ARCSolver; solver = ARCSolver();
43+
print(solver.solve_task({"train": [...], "test": [...], "metadata":
44+
{"rft_demo": {"grid": {"grid": [[0,1,0]], "targets": [[0,0],[0,1],[0,2]],
45+
"inject_output": true}}}}))'
46+
```
47+
48+
### Demo promotion proof
49+
50+
Copy-paste the snippet below to verify a promotion and trace:
51+
52+
```bash
53+
PUMA_RFT=1 python - <<'PY'
54+
from puma.rft.demo.grid import build_context
55+
from puma.rft.orchestrator import solve_with_rft
56+
from puma.rft.tracking import HypothesisMemory
57+
from puma.rft.feature_flags import build_limits
58+
from puma.rft.explain import explain_last_run
59+
60+
grid = [
61+
[0, 1],
62+
[2, 1],
63+
[2, 1],
64+
]
65+
targets = [(r, 0) for r in range(len(grid))]
66+
context, rules = build_context(grid, targets, build_limits())
67+
memory = HypothesisMemory()
68+
context, status = solve_with_rft(context, rules, memory, outer_budget=4)
69+
print(status)
70+
print(explain_last_run(context))
71+
PY
72+
```
73+
74+
Expected output (abridged):
75+
76+
```
77+
DONE
78+
Status: GOAL
79+
Top hypotheses:
80+
- propagate 0 by (1,0) regardless of color (score=1.00)
81+
Promoted rules:
82+
- track_1_0_broad (score=1.00)
83+
Metrics: ... tracking_promotions=1 ...
84+
```
85+
86+
## Observability
87+
88+
Structured debug logs are emitted under the `puma.rft.*` namespace. Metrics are
89+
collected on the context (e.g., `pliance_rule_fires`, `tracking_hypotheses_evaluated`)
90+
and surfaced via `explain_last_run`.
91+
92+
## Tracking score
93+
94+
Hypothesis tracking uses the explicit scoring rule described in the spec:
95+
96+
```
97+
progress = satisfied_targets / total_targets
98+
simplicity = len(active_conditions)
99+
score = progress - 0.1 * simplicity
100+
```
101+
102+
Scores greater than the configured threshold (`RFT_DEFAULT_LIMITS["THRESH"]`) are
103+
promoted. Simplicity penalties ensure compact rules are favoured when progress is
104+
equivalent.

puma/rft/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Relational Frame Theory inspired solving primitives for PUMA."""
2+
# [S:DESIGN v1] package=rft_core pass
3+
4+
from .feature_flags import RFT_ENABLED, RFT_DEFAULT_LIMITS, build_limits
5+
from .types import Entity, Relation, Rule, Context, Hypothesis, Limits
6+
from .pliance import pliance_solve
7+
from .tracking import (
8+
generate_variations,
9+
evaluate_hypothesis,
10+
promote_to_rule,
11+
HypothesisMemory,
12+
tracking_step,
13+
)
14+
from .orchestrator import solve_with_rft, insert_after_stuck_rule
15+
from .explain import explain_last_run, record_trace
16+
17+
__all__ = [
18+
"RFT_ENABLED",
19+
"RFT_DEFAULT_LIMITS",
20+
"build_limits",
21+
"Entity",
22+
"Relation",
23+
"Rule",
24+
"Context",
25+
"Hypothesis",
26+
"Limits",
27+
"pliance_solve",
28+
"generate_variations",
29+
"evaluate_hypothesis",
30+
"promote_to_rule",
31+
"HypothesisMemory",
32+
"tracking_step",
33+
"solve_with_rft",
34+
"insert_after_stuck_rule",
35+
"explain_last_run",
36+
"record_trace",
37+
]

0 commit comments

Comments
 (0)