Context
When a spec contains a G(boolean) constraint that is only over inputs (or that the controller cannot help satisfy), the spec is unrealizable regardless of any strategy. acacia-bonsai currently doesn't recognize this structurally — it builds the BA, runs preprocessing, and grinds through the full K-bounded safety game until it gives up. ltlsynt has a fast path for exactly this case and returns UNREALIZABLE without ever building the arena.
Concrete example: selection-ltl-2025v2/selection-ltl-2025/Light_a50cadd7.tlsf (and Warnlight_a50cadd7.tlsf). One conjunct is G !(p0b0room29light29on <-> p0b0room29light29off) — both APs are inputs, so the controller has no influence. ltlsynt reports UNREALIZABLE in ~10ms; acacia-bonsai sweeps K = 2, 5, 8, ..., 98 on three search paths (real + unreal-x-formula + unreal-x-automaton) and reports UNKNOWN.
What ltlsynt does
subprojects/spot/spot/twaalgos/synthesis.cc:1223 — try_create_direct_strategy(f, output_aps, gi, want_strategy):
- Decomposes
f into G(α) ∧ G(β) ∧ … ∧ rest, with the boolean Gs gathered into f_g.
- Builds the BDD of
f_g and computes bdd_exist(g_bdd, output_bdd). This projects out the outputs, leaving the set of input valuations for which some output choice could satisfy the constraint.
- If the result is not
bddtrue, there is an input valuation no output can rescue → return UNREALIZABLE immediately.
- Several other shortcuts for
Equiv/G F/F G patterns (lines ~1319+).
subprojects/spot/bin/ltlsynt.cc:437-449 calls this before falling through to the full game-solving pipeline (gated by opt_bypass).
Proposed change
Add an analogous fast pre-check in src/solver/solver_invoker.hh::run_one_ltl::operator(), run before the formula is negated and translated:
- On the realizability path: if the pre-check says UNREALIZABLE, the realizability child returns
false (UNKNOWN locally), but a sibling unreal child can short-circuit and report UNREAL. Even better: short-circuit at the run_ltl level and exit immediately with EXIT_CODE_UNREAL.
- On the unreal-x-* paths: the formula is the original (modulo X-pushing); the same UNREALIZABLE detection applies.
Two options for the implementation:
- Reuse spot's API directly. Call
spot::try_create_direct_strategy(spot_formula, output_aps, gi, /*want_strategy=*/false) once at the entry of the runner. If it returns realizability_code::UNREALIZABLE, declare UNREAL. Cheap, well-tested, but requires constructing a synthesis_info and threading it through (only because the spot API needs it for verbose / bv stats). For our use we'd pass a stub.
- Implement the input-only-G check ourselves. A few lines: walk the top-level
And for G(φ) with φ boolean, AND those into a single BDD over inputs∪outputs, project the outputs out, check != bddtrue. Avoids the dependency on the higher-level synthesis API.
Option (1) is simpler and inherits future improvements to the heuristic.
Where the current pipeline misses this
The empty-automaton bug (issue ##X — replace with the relevant PR/issue if filed) is a symptom of this: for the unreal-x-automaton path on Light/Warnlight, after X-pushing the formula's BA collapses to 0 states, which used to crash and now returns UNKNOWN after the recent guard patch in solver_invoker.hh. With the pre-check in place, we'd detect UNREAL before ever calling create_automaton on those subformulas.
Acceptance criteria
- A new pre-check in
run_one_ltl::operator() (or higher up in run_ltl) that, for Light_a50cadd7, Warnlight_a50cadd7, and any spec with a G-boolean input-only constraint, returns UNREAL in <100 ms without entering the K loop.
- Regression: all currently-passing benchmarks still produce the same verdicts (the pre-check only ever short-circuits to UNREAL, never claims REAL).
- A few unit tests covering: pure G-boolean over inputs, mixed G + non-G, and a control case where outputs can satisfy the constraint.
Notes
- Worth checking whether the fast path should also live in the decomposition layer — for Light, the formula is decomposed into 2 subformulas (
DECOMPOSE_SPEC=1); the input-only-G constraint is in just one of them, and any_of over the unreal paths means catching it on that subformula is enough.
- ltlsynt has additional shortcuts (Equiv, GF/FG patterns); worth scanning the rest of
try_create_direct_strategy to see which others apply to our typical inputs.
Context
When a spec contains a
G(boolean)constraint that is only over inputs (or that the controller cannot help satisfy), the spec is unrealizable regardless of any strategy. acacia-bonsai currently doesn't recognize this structurally — it builds the BA, runs preprocessing, and grinds through the full K-bounded safety game until it gives up. ltlsynt has a fast path for exactly this case and returns UNREALIZABLE without ever building the arena.Concrete example:
selection-ltl-2025v2/selection-ltl-2025/Light_a50cadd7.tlsf(andWarnlight_a50cadd7.tlsf). One conjunct isG !(p0b0room29light29on <-> p0b0room29light29off)— both APs are inputs, so the controller has no influence. ltlsynt reportsUNREALIZABLEin ~10ms; acacia-bonsai sweepsK = 2, 5, 8, ..., 98on three search paths (real + unreal-x-formula + unreal-x-automaton) and reportsUNKNOWN.What ltlsynt does
subprojects/spot/spot/twaalgos/synthesis.cc:1223—try_create_direct_strategy(f, output_aps, gi, want_strategy):fintoG(α) ∧ G(β) ∧ … ∧ rest, with the boolean Gs gathered intof_g.f_gand computesbdd_exist(g_bdd, output_bdd). This projects out the outputs, leaving the set of input valuations for which some output choice could satisfy the constraint.bddtrue, there is an input valuation no output can rescue → returnUNREALIZABLEimmediately.Equiv/G F/F Gpatterns (lines ~1319+).subprojects/spot/bin/ltlsynt.cc:437-449calls this before falling through to the full game-solving pipeline (gated byopt_bypass).Proposed change
Add an analogous fast pre-check in
src/solver/solver_invoker.hh::run_one_ltl::operator(), run before the formula is negated and translated:false(UNKNOWN locally), but a sibling unreal child can short-circuit and report UNREAL. Even better: short-circuit at therun_ltllevel and exit immediately withEXIT_CODE_UNREAL.Two options for the implementation:
spot::try_create_direct_strategy(spot_formula, output_aps, gi, /*want_strategy=*/false)once at the entry of the runner. If it returnsrealizability_code::UNREALIZABLE, declare UNREAL. Cheap, well-tested, but requires constructing asynthesis_infoand threading it through (only because the spot API needs it for verbose / bv stats). For our use we'd pass a stub.AndforG(φ)withφboolean, AND those into a single BDD over inputs∪outputs, project the outputs out, check!= bddtrue. Avoids the dependency on the higher-level synthesis API.Option (1) is simpler and inherits future improvements to the heuristic.
Where the current pipeline misses this
The empty-automaton bug (issue ##X — replace with the relevant PR/issue if filed) is a symptom of this: for the unreal-x-automaton path on Light/Warnlight, after X-pushing the formula's BA collapses to 0 states, which used to crash and now returns
UNKNOWNafter the recent guard patch insolver_invoker.hh. With the pre-check in place, we'd detect UNREAL before ever callingcreate_automatonon those subformulas.Acceptance criteria
run_one_ltl::operator()(or higher up inrun_ltl) that, forLight_a50cadd7,Warnlight_a50cadd7, and any spec with a G-boolean input-only constraint, returns UNREAL in <100 ms without entering the K loop.Notes
DECOMPOSE_SPEC=1); the input-only-G constraint is in just one of them, andany_ofover the unreal paths means catching it on that subformula is enough.try_create_direct_strategyto see which others apply to our typical inputs.