# fret_to_synth.py -- FRET Export to Synthesis Config ## Purpose Converts a FRET JSON export into a synthesis configuration file that `ltlsynt` (from the Spot library) can consume. Handles the critical encoding step: FRET's multi-valued mode variables become independent boolean propositions with automatically generated mutual exclusion constraints. ## Usage ```bash python3 scripts/fret_to_synth.py [output.json] ``` If `output.json` is omitted, writes to `specs/synthesis_config_v3.json`. ## How It Works ### Step 1: Mode Discovery (`discover_modes`) Regex-scans all `ftInfAUExpanded` (and fallback `ft`) LTL formulas in the FRET export for patterns matching: ``` (control_GROUPNAME = q_VALUENAME) ``` Returns a dictionary of mode groups: ```python { "control_mode": ["q_heatup", "q_operation", "q_scram", "q_shutdown"], "control_pump": ["q_off", "q_running"], # if present } ``` This is fully automatic -- no hardcoded variable lists. Adding a new mode in FRET and re-exporting is all that's needed. ### Step 2: Boolean Encoding (`build_mode_map`) For each mode group, creates boolean proposition names: ``` control_mode = q_shutdown --> in_mode_shutdown control_mode = q_heatup --> in_mode_heatup control_pump = q_off --> in_pump_off ``` The naming rule is: `in_{group_suffix}_{value_suffix}`, where the group suffix strips `control_` and the value suffix strips `q_`. Returns a string replacement map used by `encode_ltl` to transform the FRET LTL formulas. ### Step 3: Variable Classification (`classify_variables`) Every variable in the FRET semantics is classified: - **Mode variables** (`control_*`, `q_*`): handled by boolean encoding, excluded from explicit input/output lists. - **Everything else**: classified as an environment input. These are typically continuous-domain predicates that have been abstracted to boolean (e.g., `t_avg_above_min`, `inv1_holds`). ### Step 4: LTL Encoding (`encode_ltl`) Simple string replacement of `(control_X = q_Y)` with the corresponding boolean name. A sanity check ensures no `control_` or `q_` references survive the transformation -- if a mode value appears in the LTL but wasn't discovered in Step 1, the script raises an error rather than producing a silent bug. ### Step 5: Mutual Exclusion (`mutual_exclusion_ltl`) For each mode group, generates an "exactly one active" LTL constraint: ``` G( (a & !b & !c & !d) | (!a & b & !c & !d) | (!a & !b & c & !d) | (!a & !b & !c & d) ) ``` This is conjoined with the requirement formulas in the final output. **Why this is needed:** FRET's `control_mode = q_X` is a single integer variable -- it inherently holds one value. After decomposition into independent booleans, the synthesizer could assert `in_mode_heatup = true` AND `in_mode_scram = true` simultaneously and still satisfy every requirement. The mutual exclusion constraint prevents this. It is a synthesis encoding artifact, not a gap in the FRET specifications. ### Step 6: LTL Formula Selection The script prefers `ftInfAUExpanded` from FRET's semantics object. This is the future-time LTL formula with: - Infinite-horizon semantics (no LAST boundary terms) - Expanded AU (always-until) operators Fallback is `ft` (the raw future-time formula, which may contain LAST operators). The `ftInfAUExpanded` form produces cleaner synthesis results because ltlsynt works over infinite traces. ## Output Format The generated JSON contains: | Field | Description | |---|---| | `spec_name` | `{PROJECT}_{COMPONENT}` from the FRET export | | `mode_groups` | Discovered control groups, their values, and boolean names | | `inputs` | Environment (uncontrollable) variables | | `outputs` | System (controllable) boolean mode propositions | | `requirements` | Each FRET requirement with original and encoded LTL | | `structural_constraints` | Auto-generated mutual exclusion constraints | | `conjoined_ltl` | All formulas AND'd together, ready for ltlsynt | ## Future Improvements - **Multi-valued non-mode variables**: Currently assumes all non-mode variables are boolean. If FRET exports integer-valued environment variables (e.g., `power_level = 3`), the encoding would need range-based boolean decomposition. - **Environment assumptions**: No mechanism yet for adding assumptions about the environment (e.g., "t_max_exceeded cannot persist forever"). These would need to be separated from guarantees in the ltlsynt call (`--ins` assumptions vs. `--outs` guarantees). This is important for liveness properties. - **Liveness constraints**: The old synthesis config had a manual `F(heatup)` liveness constraint. Currently there is no automatic detection of which requirements imply liveness vs. safety. A future version could parse FRET's timing semantics to classify these. - **Partial observability**: All environment variables are currently assumed fully observable. Real reactor instrumentation has sensor noise and delays. Extending the pipeline to handle partial observability would require belief-state synthesis.