Folds three previously-separate pieces into one preliminary-example repo for the HAHACS thesis: - thesis/ (submodule) → gitea Thesis.git — the PhD proposal - fret-pipeline/ — FRET requirements to AIGER controller (was ~/Documents/fret_processing/; prior single-commit history abandoned per user decision) - plant-model/ — 10-state PKE + lumped T/H PWR model (was ~/Documents/PKE_Playground/; never version-controlled before) - presentations/2026DICE/ (submodule) → gitea 2026DICE.git - reachability/, hardware/ — empty placeholders for Thrust 3 and HIL - docs/architecture.md — how the discrete and continuous layers compose - claude_memory/ — session notes and scratch knowledge pattern Plant model refactored to thesis naming (x, plant, u, ref); pke_th_rhs now takes u as an explicit arg instead of reading rho_ext from the params struct. First two controllers built to the contract u = ctrl_<mode>(t, x, plant, ref): ctrl_null (baseline) and ctrl_operation (stabilizing, proportional on T_avg). Validated under a 100% -> 80% Q_sg step: ctrl_operation reduces steady-state T_avg drift ~47% vs. the unforced plant. Root CLAUDE.md emphasizes that CLAUDE.md files are living documents and that any knowledge not captured before a session ends is lost forever; claude_memory/ holds the session-level notes that haven't stabilized enough to graduate into a CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.0 KiB
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
python3 scripts/fret_to_synth.py <fret_export.json> [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:
{
"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 (
--insassumptions vs.--outsguarantees). 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.