Skip to content
Merged

V2 #25

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Summary
- [ ] What changed?
- [ ] Why?

## Testing
- [ ] Steps
- [ ] Results

## Risk Assessment
- [ ] Regressions considered
- [ ] Rollback plan

[S:PR v1] template=installed pass
56 changes: 56 additions & 0 deletions arc_solver/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
synthesize as synth_baseline,
predict_two as predict_two_baseline,
)
from puma.rft.feature_flags import RFT_ENABLED, build_limits
from puma.rft.orchestrator import solve_with_rft
from puma.rft.tracking import HypothesisMemory
from puma.rft.demo.grid import build_context as build_rft_grid_context
from puma.rft.explain import explain_last_run
from .enhanced_search import synthesize_with_enhancements, predict_two_enhanced
from .hypothesis import HypothesisEngine, Hypothesis
from .continuous_learning import ContinuousSelfMemory
Expand Down Expand Up @@ -79,6 +84,10 @@ def __init__(self, use_enhancements: bool = True,
self._placeholder_templates: List[PlaceholderTemplate] = []
self._new_placeholder_templates: List[PlaceholderTemplate] = []
self._last_hypotheses: List[Hypothesis] = []
self.rft_enabled = RFT_ENABLED
self._rft_limits = build_limits()
self._rft_memory = HypothesisMemory()
self._last_rft_status: Optional[str] = None

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

def _get_predictions(
Expand Down Expand Up @@ -216,6 +228,50 @@ def _get_predictions(
self.logger.info("Using baseline prediction")
return baseline

def _maybe_run_rft_adapter(
self,
task: Dict[str, Any],
attempt1: List[List[List[int]]],
attempt2: List[List[List[int]]],
) -> Tuple[List[List[List[int]]], List[List[List[int]]]]:
"""Optionally run the RFT orchestrator when the demo flag is present."""

metadata = task.get("metadata") or {}
rft_config = metadata.get("rft_demo")
if not rft_config or "grid" not in rft_config:
return attempt1, attempt2
grid_spec = rft_config["grid"]
grid = grid_spec.get("grid")
targets = grid_spec.get("targets", [])
if not isinstance(grid, list) or not grid:
return attempt1, attempt2
try:
context, rules = build_rft_grid_context(grid, targets, limits=self._rft_limits)
except Exception:
return attempt1, attempt2
context, status = solve_with_rft(
context,
rules,
self._rft_memory,
outer_budget=self._rft_limits.outer_budget,
)
self._last_rft_status = status
if self.enable_logging:
self.logger.debug("RFT status=%s\n%s", status, explain_last_run(context))
if status != "DONE" or not grid_spec.get("inject_output", False):
return attempt1, attempt2
solved_grid = context.state.grid
if solved_grid and isinstance(solved_grid[0][0], str):
palette: Dict[str, int] = {"black": 0}
next_id = 1
for row in solved_grid:
for color in row:
if color not in palette:
palette[color] = next_id
next_id += 1
solved_grid = [[palette[color] for color in row] for row in solved_grid]
return [solved_grid for _ in attempt1], [solved_grid for _ in attempt2]

def _postprocess_predictions(
self,
train_pairs: List[Tuple[Array, Array]],
Expand Down
10 changes: 10 additions & 0 deletions puma/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""PUMA meta-learning components."""
from importlib import import_module

__all__ = ["rft"]


def __getattr__(name):
if name == "rft":
return import_module("puma.rft")
raise AttributeError(name)
104 changes: 104 additions & 0 deletions puma/rft/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# PUMA RFT Module

[S:DOC v2] scope=puma.rft overview=complete pass

The Relational Frame Theory (RFT) module adds pliance-based rule following and
tracking-driven rule variation to the PUMA solver while remaining fully
feature-flagged behind `PUMA_RFT`.

## Architecture

```
+-------------------+ +----------------------+ +------------------+
| feature_flags | ---> | pliance (determinism)| ---> | explain |
+-------------------+ +----------------------+ +------------------+
| | ^
v v |
+-------------------+ +----------------------+ +------------------+
| demo/grid harness | ---> | tracking (variation) | ---> | orchestrator |
+-------------------+ +----------------------+ +------------------+
```

* `types.py` exposes typed primitives (rules, context, hypotheses).
* `pliance.py` executes prioritized rules deterministically.
* `tracking.py` evaluates adaptive hypotheses and promotes successful ones.
* `orchestrator.py` alternates pliance and tracking with metrics and guards.
* `explain.py` records rich traces surfaced via `explain_last_run()`.

## Usage

1. Enable the feature flag: `export PUMA_RFT=1`.
2. Run the demo tests: `pytest tests/rft/test_rft_integration.py -q`.
3. Inspect the most recent trace:

```python
from puma.rft.explain import explain_last_run
print(explain_last_run())
```

To exercise the adapter inside the ARC solver:

```bash
PUMA_RFT=1 python -c 'from arc_solver import ARCSolver; solver = ARCSolver();
print(solver.solve_task({"train": [...], "test": [...], "metadata":
{"rft_demo": {"grid": {"grid": [[0,1,0]], "targets": [[0,0],[0,1],[0,2]],
"inject_output": true}}}}))'
```

### Demo promotion proof

Copy-paste the snippet below to verify a promotion and trace:

```bash
PUMA_RFT=1 python - <<'PY'
from puma.rft.demo.grid import build_context
from puma.rft.orchestrator import solve_with_rft
from puma.rft.tracking import HypothesisMemory
from puma.rft.feature_flags import build_limits
from puma.rft.explain import explain_last_run

grid = [
[0, 1],
[2, 1],
[2, 1],
]
targets = [(r, 0) for r in range(len(grid))]
context, rules = build_context(grid, targets, build_limits())
memory = HypothesisMemory()
context, status = solve_with_rft(context, rules, memory, outer_budget=4)
print(status)
print(explain_last_run(context))
PY
```

Expected output (abridged):

```
DONE
Status: GOAL
Top hypotheses:
- propagate 0 by (1,0) regardless of color (score=1.00)
Promoted rules:
- track_1_0_broad (score=1.00)
Metrics: ... tracking_promotions=1 ...
```

## Observability

Structured debug logs are emitted under the `puma.rft.*` namespace. Metrics are
collected on the context (e.g., `pliance_rule_fires`, `tracking_hypotheses_evaluated`)
and surfaced via `explain_last_run`.

## Tracking score

Hypothesis tracking uses the explicit scoring rule described in the spec:

```
progress = satisfied_targets / total_targets
simplicity = len(active_conditions)
score = progress - 0.1 * simplicity
```

Scores greater than the configured threshold (`RFT_DEFAULT_LIMITS["THRESH"]`) are
promoted. Simplicity penalties ensure compact rules are favoured when progress is
equivalent.
37 changes: 37 additions & 0 deletions puma/rft/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Relational Frame Theory inspired solving primitives for PUMA."""
# [S:DESIGN v1] package=rft_core pass

from .feature_flags import RFT_ENABLED, RFT_DEFAULT_LIMITS, build_limits
from .types import Entity, Relation, Rule, Context, Hypothesis, Limits
from .pliance import pliance_solve
from .tracking import (
generate_variations,
evaluate_hypothesis,
promote_to_rule,
HypothesisMemory,
tracking_step,
)
from .orchestrator import solve_with_rft, insert_after_stuck_rule
from .explain import explain_last_run, record_trace

__all__ = [
"RFT_ENABLED",
"RFT_DEFAULT_LIMITS",
"build_limits",
"Entity",
"Relation",
"Rule",
"Context",
"Hypothesis",
"Limits",
"pliance_solve",
"generate_variations",
"evaluate_hypothesis",
"promote_to_rule",
"HypothesisMemory",
"tracking_step",
"solve_with_rft",
"insert_after_stuck_rule",
"explain_last_run",
"record_trace",
]
Loading
Loading