# EXPERIMENT_RUNNER.md

`./grownet.sh --lang python --config ...` runs a config-driven GrowNet experiment using the real Python runtime.

## Purpose

The experiment runner is the shared non-interactive config path for:

- batch run mode
- trace mode
- benchmark mode
- interactive scaffold loading

The config defines the initial scaffold, input-space declaration, input source, optional focus controls, and optional output targets. GrowNet still remains free to grow after the run starts.

Focus & Anchoring V1 can also be enabled from the same config model. See [FOCUS_AND_ANCHORING.md](C:/Development/Projects/GrowNet/docs/FOCUS_AND_ANCHORING.md).

## CLI Flags

- `--config path/to/experiment.json`
- `--input path/to/input_file`
- `--input-type sequence|tensor|image|audio`
- `--output path/to/output_file_or_dir`
- `--mode run|trace|benchmark|interactive`

Existing mode flags still work:

- `--trace`
- `--benchmark`
- `--interactive`

Current precedence:

1. explicit CLI mode flags
2. `--mode`
3. config runtime defaults

## Example Configs

Checked-in examples:

- [single_scalar_sequence.json](C:/Development/Projects/GrowNet/experiments/single_scalar_sequence.json)
- [dual_scalar_sequence.json](C:/Development/Projects/GrowNet/experiments/dual_scalar_sequence.json)
- [image_tensor.json](C:/Development/Projects/GrowNet/experiments/image_tensor.json)

## Config Shape

Example:

```json
{
  "name": "dual_scalar_probe",
  "runtime": {
    "ticks": 3,
    "trace": false,
    "trace_format": "jsonl",
    "benchmark": false,
    "interactive": false
  },
  "interactive": {
    "output": "compact",
    "trace": false,
    "normalize_inputs": "normalized"
  },
  "focus": {
    "enabled": true,
    "policy": "energy_first",
    "serial": true,
    "candidate_limit": 4,
    "allow_anchor_map": true,
    "inhibition_of_return_ticks": 3
  },
  "input_space": {
    "rank": 1,
    "shape": [2],
    "ports": [
      {"name": "a"},
      {"name": "b"}
    ]
  },
  "network": {
    "regions": [
      {
        "name": "main",
        "layers": [
          {
            "excitatory_count": 20,
            "inhibitory_count": 4,
            "modulatory_count": 2
          }
        ]
      }
    ]
  },
  "input": {
    "type": "sequence",
    "values": [[0.1, 0.7], [0.5, 0.3]]
  },
  "output": {
    "trace_file": "out/trace.jsonl",
    "metrics_file": "out/metrics.json",
    "benchmark_file": "out/benchmark.jsonl"
  }
}
```

## Scaffold Rules

The config currently supports:

- one or more regions
- one or more layers per region
- per-layer `excitatory_count`, `inhibitory_count`, `modulatory_count`
- optional per-layer `neuron_limit`
- optional explicit `connections`
- optional explicit `input_bindings`
- optional per-region growth-policy overrides when the Python runtime already supports them
- optional experiment-level `focus` controls for serial candidate selection and anchor-map behavior

If a region has multiple layers and no explicit `connections`, consecutive layers auto-connect with probability `1.0`.

If a region has no explicit `input_bindings`, every configured input port binds to layer `0`.

## Input-Space Rules

The runner makes dimensionality explicit through `input_space.rank` and `input_space.shape`.

- rank `1`: scalar ports or 1D tensors
- rank `2`: 2D tensor/image path
- rank `3`: 3D tensor path

Current shaped-port limitation:

- rank `2` and rank `3` currently support exactly one configured input port per experiment config

## Focus Block

Current focus config fields:

- `enabled`
- `policy`
- `serial`
- `candidate_limit`
- `allow_anchor_map`
- `inhibition_of_return_ticks`
- `anchor_map_capacity`
- `anchor_decay_ticks`

Current policies:

- `energy_first`
- `random_candidate`
- `sequential_scan`
- `novelty_first`
- `familiarity_first`

Current execution behavior:

- rank `2` experiments can route through the focused 2D path when `focus.enabled` is true
- rank `1` experiments still use the normal scalar runtime path, but scalar anchoring remains explicit in runtime summaries
- rank `3` does not yet have a focused volume-selection path in this PR

## Input Types

### `sequence`

Fully supported.

- single scalar port: one scalar value per tick
- multi-port scalar rank-1 input: one vector/object per tick
- file-backed sequence: `.json`, `.jsonl`, `.csv`, or `.txt`

### `tensor`

Fully supported.

- rank-1, rank-2, and rank-3 JSON tensors
- a single tensor means one tick
- a list of tensors means one tensor per tick

### `image`

Adapter support.

- grayscale path only in this PR
- `.pgm` works without extra dependencies
- other image formats use Pillow when available
- optional nearest-neighbor `resize`
- internally converted into the same structured tensor path used by rank-2 inputs

### `audio`

Adapter support.

- WAV PCM path in this PR
- audio is framed into rank-1 windows
- one frame equals one tick
- sample-rate mismatch is rejected; resampling is not implemented in this PR
- internally converted into the same structured tensor path used by rank-1 tensor inputs

## Batch Semantics

### Sequence input

- single scalar port: one scalar per tick
- multi-port scalar input: one vector/object per tick

### Tensor input

- one tensor = one tick
- list of tensors = one tensor per tick

### Image input

- one image = one tick
- if `runtime.ticks` is greater than `1`, the same image is replayed for that many ticks

### Audio input

- one framed window = one tick

If the configured tick count exceeds the available input payloads, the runner raises an explicit error unless the input is a single tensor/image that is being replayed intentionally.

## Output Persistence

Config output targets are optional.

- `output.trace_file`: default trace file for trace mode
- `output.metrics_file`: run summary JSON for batch run mode
- `output.benchmark_file`: default benchmark output file

`--output` behavior:

- run mode: overrides the metrics summary file
- trace mode: acts as the trace output file when `--out` is not explicitly set
- benchmark mode: acts as the benchmark output file when `--benchmark-output` is not explicitly set
- if the target is a directory, the runner writes `metrics.json`, `trace.log` / `trace.jsonl`, or `benchmark.log` / `benchmark.jsonl` inside that directory

## Example Commands

Batch run:

```bash
./grownet.sh --lang python --config experiments/single_scalar_sequence.json
```

Trace with config:

```bash
./grownet.sh --lang python --trace --trace-format jsonl --config experiments/single_scalar_sequence.json
```

Focused trace with config:

```bash
./grownet.sh --lang python --trace --trace-format jsonl --trace-events focus,anchor --config experiments/image_tensor.json
```

Benchmark with config:

```bash
./grownet.sh --lang python --benchmark --config experiments/single_scalar_sequence.json --benchmark-warmup 25 --benchmark-iterations 250 --benchmark-repeat 3
```

Interactive with config:

```bash
./grownet.sh --lang python --interactive --config experiments/dual_scalar_sequence.json
```

## Scope Notes

- Python is the only language with full config-driven experiment execution in this PR.
- Java scenario-based trace and benchmark mode remain available.
- Java config-driven execution currently returns a clear not-implemented message from the launcher.
