---
phase: 04-integration-publishing
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - examples/cifar10.py
autonomous: true

must_haves:
  truths:
    - "Running `python examples/cifar10.py` trains a PCN on CIFAR-10 without crashing"
    - "The example prints a summary after training: test accuracy, training time, energy convergence"
    - "The example uses RichCallback for live progress display during training"
    - "Dataset download failure produces a friendly error message (not a raw traceback)"
    - "The example uses only public pcn_torch API (no private names)"
  artifacts:
    - path: "examples/cifar10.py"
      provides: "Standalone CIFAR-10 classification example"
      min_lines: 70
      contains: "RichCallback"
    - path: "examples/cifar10.py"
      provides: "Network construction"
      contains: "dims=[3072, 1024, 1024, 512]"
  key_links:
    - from: "examples/cifar10.py"
      to: "pcn_torch public API"
      via: "from pcn_torch import ..."
      pattern: "from pcn_torch import"
    - from: "examples/cifar10.py"
      to: "trainer internals"
      via: "NO Flatten transform — trainer.py handles view(B, -1) internally"
      pattern: "transforms.Compose"
---

<objective>
Create examples/cifar10.py — a self-contained script that trains a PredictiveCodingNetwork on CIFAR-10 and prints a full training summary.

Purpose: This is the primary demonstration of the library. Users run it to verify the installation works and to see predictive coding in action on a real dataset.

Output: examples/cifar10.py with fixed configuration, RichCallback progress display, and a printed summary after training.
</objective>

<execution_context>
@C:\Users\RDCHLEM1\.claude/get-shit-done/workflows/execute-plan.md
@C:\Users\RDCHLEM1\.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/phases/04-integration-publishing/04-CONTEXT.md
@.planning/phases/04-integration-publishing/04-RESEARCH.md
@src/pcn_torch/__init__.py
@src/pcn_torch/trainer.py
@src/pcn_torch/network.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create examples/cifar10.py with CIFAR-10 training script</name>
  <files>examples/cifar10.py</files>
  <action>
Create the `examples/` directory and `examples/cifar10.py` with exactly this structure and content (per locked decisions):

**File structure:**

```python
"""CIFAR-10 classification with pcn-torch.

Run:
    python examples/cifar10.py

Trains a 3-hidden-layer Predictive Coding Network on CIFAR-10 using local
Hebbian-like update rules (no backpropagation). Achieves approximately
45-55% test accuracy in 5-15 minutes on CPU.

Requirements (for this example only):
    pip install torchvision
"""

import time

import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

from pcn_torch import (
    PredictiveCodingNetwork,
    RichCallback,
    TrainConfig,
    test_pcn,
    train_pcn,
)

# ---------------------------------------------------------------------------
# Configuration (fixed — no CLI arguments by design)
# ---------------------------------------------------------------------------
BATCH_SIZE = 128
NUM_EPOCHS = 10
T_INFER = 20       # 20 inference steps per batch (paper uses 50; 20 saves ~60% CPU time)
LR_INFER = 0.05
LR_LEARN = 0.001
DATA_ROOT = "./data"

# CIFAR-10 channel statistics (per-channel mean and std)
CIFAR10_MEAN = (0.4914, 0.4822, 0.4465)
CIFAR10_STD = (0.2023, 0.1994, 0.2010)


def load_cifar10() -> tuple[DataLoader, DataLoader]:
    """Download CIFAR-10 and return train and test DataLoaders."""
    # NOTE: No Flatten() transform here — trainer.py calls x_batch.view(B, -1) internally.
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(CIFAR10_MEAN, CIFAR10_STD),
    ])
    try:
        train_set = torchvision.datasets.CIFAR10(
            root=DATA_ROOT, train=True, download=True, transform=transform
        )
        test_set = torchvision.datasets.CIFAR10(
            root=DATA_ROOT, train=False, download=True, transform=transform
        )
    except Exception as exc:
        raise SystemExit(
            f"Failed to load CIFAR-10 dataset: {exc}\n"
            "Check your internet connection or download the dataset manually."
        ) from exc

    train_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=0)
    test_loader = DataLoader(test_set, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)
    return train_loader, test_loader


def build_model() -> PredictiveCodingNetwork:
    """Build the CIFAR-10 PCN model.

    Architecture: input(3072) -> hidden(1024) -> hidden(1024) -> top_latent(512) -> readout(10)
    dims[0] = input dimension (3 x 32 x 32 = 3072)
    dims[-1] = top latent dimension (512)
    output_dim = number of classes (10)
    """
    return PredictiveCodingNetwork(
        dims=[3072, 1024, 1024, 512],
        activation="relu",
        output_dim=10,
        mode="classification",
    )


def main() -> None:
    """Train a PCN on CIFAR-10 and print a summary."""
    print("Loading CIFAR-10...")
    train_loader, test_loader = load_cifar10()

    print("Building model: dims=[3072, 1024, 1024, 512], output_dim=10")
    model = build_model()

    config = TrainConfig(
        task="classification",
        T_infer=T_INFER,
        lr_infer=LR_INFER,
        lr_learn=LR_LEARN,
        num_epochs=NUM_EPOCHS,
        early_stop_threshold=1e-4,
        callback=RichCallback(),
    )

    # Train
    start = time.time()
    history = train_pcn(model, train_loader, config)
    elapsed = time.time() - start

    # Evaluate on test set
    results = test_pcn(model, test_loader, config)

    # Print summary
    print()
    print("=" * 52)
    print("  CIFAR-10 Training Summary")
    print("=" * 52)
    print(f"  Final test accuracy:  {results['accuracy']:.1%}")
    print(f"  Total training time: {elapsed / 60:.1f} minutes")
    if history.energy.per_epoch:
        print(f"  Energy (epoch 1):    {history.energy.per_epoch[0]:.6f}")
        print(f"  Energy (final):      {history.energy.per_epoch[-1]:.6f}")
        convergence = history.energy.per_epoch[0] - history.energy.per_epoch[-1]
        print(f"  Energy reduction:    {convergence:.6f}")
    print("=" * 52)


if __name__ == "__main__":
    main()
```

**Key constraints (per locked decisions and research pitfalls):**
- No Flatten() in transforms — trainer.py does view(B, -1) internally
- dims=[3072, 1024, 1024, 512] NOT [10, 512, ...] (input-first ordering)
- Use RichCallback() (not default callback) per locked decision
- Fixed config constants at top — no argparse, no CLI
- Error handling wraps dataset loading with SystemExit + friendly message
- num_workers=0 for cross-platform compatibility (Windows multiprocessing limitation)
- Use test_pcn (from public API) not any private evaluation function
  </action>
  <verify>
```bash
# 1. Verify file was created
ls examples/cifar10.py
# Expected: file exists

# 2. Verify no Flatten transform (would be wrong — trainer handles flattening)
grep "Flatten" examples/cifar10.py
# Expected: no output

# 3. Verify dims ordering (input-first)
grep "dims=\[3072" examples/cifar10.py
# Expected: dims=[3072, 1024, 1024, 512]

# 4. Verify RichCallback is used (locked decision)
grep "RichCallback" examples/cifar10.py
# Expected: RichCallback() appears in config

# 5. Verify script has no syntax errors
uv run python -m py_compile examples/cifar10.py
# Expected: exits 0, no output

# 6. Verify ruff passes on the example
uv run ruff check examples/cifar10.py
# Expected: All checks passed, no issues found
# If ruff flags anything, fix it before committing.

# 7. Verify public imports resolve (without actually downloading CIFAR-10)
uv run python -c "
from pcn_torch import PredictiveCodingNetwork, RichCallback, TrainConfig, test_pcn, train_pcn
print('All imports OK')
"
# Expected: All imports OK
```
  </verify>
  <done>
- examples/cifar10.py exists with correct structure
- No Flatten transform (trainer handles internally)
- dims=[3072, 1024, 1024, 512] (input-first)
- RichCallback() used per locked decision
- Fixed config constants (no CLI args) per locked decision
- Dataset error handling wraps the try/except with SystemExit + friendly message
- `python -m py_compile examples/cifar10.py` passes
- `ruff check examples/cifar10.py` passes
  </done>
</task>

</tasks>

<verification>
```bash
# Full import smoke test: can the example be imported as a module
uv run python -c "
import importlib.util
spec = importlib.util.spec_from_file_location('cifar10', 'examples/cifar10.py')
mod = importlib.util.module_from_spec(spec)
# Don't execute main(), just check the module loads
print('Module structure valid')
"
# Expected: Module structure valid (no import errors at module level)

# Confirm examples/ is NOT in the wheel (hatchling uses packages = ["src/pcn_torch"])
# This is already correct by default — just confirm
grep -A 3 "targets.wheel" pyproject.toml
# Expected: packages = ["src/pcn_torch"]  (examples/ excluded)
```
</verification>

<success_criteria>
- examples/cifar10.py created with all locked decisions implemented
- Fixed configuration (BATCH_SIZE=128, NUM_EPOCHS=10, T_INFER=20, LR_INFER=0.05, LR_LEARN=0.001)
- RichCallback used for live progress display
- Full summary printed after training (accuracy, time, energy convergence)
- Dataset error wrapped in try/except with friendly message
- No Flatten transform (trainer.py does view(B, -1) internally)
- ruff check passes on the example file
</success_criteria>

<output>
After completion, create `.planning/phases/04-integration-publishing/04-02-SUMMARY.md`
</output>
