# FRET-to-Synthesis Pipeline

A deterministic pipeline for converting FRET natural-language requirements into
synthesized reactive controllers, with automatic state machine visualization.

```
pwr_hybrid_3.json              FRET export (source of truth)
        |
        v
scripts/fret_to_synth.py       Auto-discover modes, encode as booleans
        |
        v
specs/synthesis_config_v3.json  ltlsynt-ready config
        |
        v
scripts/synthesize.sh           Run ltlsynt (reactive synthesis)
        |
        v
circuits/*.aag                  Synthesized controller (AIGER circuit)
        |
        v
scripts/trace_aiger.py          Exhaustive eval, guard extraction, DOT
        |
        v
diagrams/*_states.dot/png/svg   State machine with guard conditions
```

## Quick Start

```bash
# 1. Convert FRET export to synthesis config
python3 scripts/fret_to_synth.py pwr_hybrid_3.json specs/synthesis_config_v3.json

# 2. Synthesize the controller
bash scripts/synthesize.sh specs/synthesis_config_v3.json circuits

# 3. Trace and visualize
python3 scripts/trace_aiger.py circuits/PWR_HYBRID_3_DRC.aag diagrams
dot -Tpng diagrams/PWR_HYBRID_3_DRC_states.dot -o diagrams/PWR_HYBRID_3_DRC_states.png
```

## Prerequisites

- **Python 3.10+** (no external packages required)
- **[Spot](https://spot.lre.epita.fr/)** for `ltlsynt` (`brew install spot`)
- **[Graphviz](https://graphviz.org/)** for `dot` (`brew install graphviz`)

## FRET Naming Conventions

The pipeline auto-discovers mode variables and environment inputs from your FRET
export. To make this work, follow these naming rules in FRET:

### Mode variables: `control_* = q_*`

Any variable whose name starts with `control_` is treated as a discrete mode
variable. Its values must start with `q_`. Examples:

```
control_mode = q_shutdown      (a reactor operating mode)
control_pump = q_running       (a pump state)
control_valve = q_open         (a valve position)
```

The script discovers these by regex-scanning the LTL formulas. Multiple control
groups are supported -- each gets its own set of boolean outputs and its own
mutual exclusion constraint.

### Everything else is an environment input

Variables that don't match `control_*` or `q_*` are classified as environment
inputs (uncontrollable). These are typically continuous predicates abstracted
to booleans:

```
t_avg_above_min    (temperature predicate)
inv1_holds         (safety invariant)
manual_reset       (operator action)
```

### Mutual exclusion encoding

FRET uses a single integer variable (`control_mode`) that inherently can only
hold one value at a time. But ltlsynt works with independent boolean
propositions. When we decompose `control_mode` into separate booleans
(`in_mode_shutdown`, `in_mode_heatup`, etc.), we lose the single-valued
constraint -- the synthesizer could assert multiple modes simultaneously.

The mutual exclusion constraint is added automatically by `fret_to_synth.py`
to restore this invariant. **It is a synthesis encoding artifact, NOT a missing
FRET requirement.** You do not need to write it in FRET.

## Directory Structure

```
fret_processing/
  pwr_hybrid_3.json          FRET JSON export
  specs/
    synthesis_config_v3.json  Generated synthesis config
  circuits/
    PWR_HYBRID_3_DRC.aag     Synthesized AIGER circuit
  diagrams/
    *_states.dot/png/svg     State machine diagrams
  scripts/
    fret_to_synth.py         FRET-to-ltlsynt converter
    synthesize.sh            ltlsynt wrapper
    trace_aiger.py           Circuit tracer + visualizer
    aag2dot.py               Gate-level circuit diagram
    visualize.sh             Visualization wrapper
  .archive/                  Old/superseded files
```
