Working with LTL formulas with finite semantics
Table of Contents
The LTL operators used by Spot are defined over infinite words, and the various type of automata supported are all ω-automata, i.e., automata over infinite words.
However, there is a trick we can use in case we want to use Spot to build a finite automaton that recognize some LTLf (i.e. LTL with finite semantics) property. The plan is as follows:
- Have Spot read the input LTLf formula as if it was LTL.
- Rewrite this formula in a way that embeds the semantics of LTLf in
LTL. First, introduce a new atomic proposition
alive
that will be true initially, but that will eventually become false forever. Then adjust all original LTL operators so that they have to be satisfied during thealive
part of the word. For instance the formula(a U b) & Fc
would be transformed intoalive & (a U (alive & b)) & F(alive & c) & (alive U G!alive)
. Convert the resulting formula into a Büchi automaton:
Remove the
alive
property, after marking as accepting all states with an outgoing edge labeled by!alive
. (Note that since Spot does not actually support state-based acceptance, it needs to keep a false self-loop on any accepting state without a successor in order to mark it as accepting.)- Interpret the above automaton as finite automaton. (This part is out of scope for Spot.)
The above sequence of operations was described by De Giacomo & Vardi
in their IJCAI'13 paper and by Dutta & Vardi in their Memocode'14
paper. However, beware that the LTLf to LTL rewriting suggested in
theorem 1 of the IJCAI'13 paper has a typo (t(φ₁ U φ₂)
should be
equal to t(φ₁) U t(φ₂ & alive)
) that is fixed in the Memocode'14
paper, but that second paper forgets to ensure that alive
holds
initially, as required in the first paper…
Shell version
The first four steps of the above sequence of operations can be
executed as follows. Transforming LTLf to LTL can be done using
ltlfilt
's --from-ltlf
option, translating the resulting formula
into a Büchi automaton is obviously done with ltl2tgba
, and removing
an atomic proposition while adapting the accepting states can be done
with autfilt
's --to-finite
option. Interpreting the resulting
Büchi automaton as a finite automaton is out of scope for Spot.
ltlfilt --from-ltlf -f "(a U b) & Fc" |
ltl2tgba -B |
autfilt --to-finite
HOA: v1 States: 4 Start: 2 AP: 3 "b" "a" "c" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc deterministic --BODY-- State: 0 [!2] 0 [2] 1 State: 1 {0} [t] 1 State: 2 [0&!2] 0 [0&2] 1 [!0&1&!2] 2 [!0&1&2] 3 State: 3 [0] 1 [!0&1] 3 --END--
Use -B -D
instead of -B
if you want to ensure that a deterministic
automaton is output.
Python version
In Python, we use the from_ltlf()
function to convert from LTLf to
LTL and translate the result into a Büchi automaton using
translate()
as usual. Then we need to call the to_finite()
function.
import spot # Translate LTLf to Büchi. f = spot.from_ltlf('(a U b) & Fc') aut = f.translate('small', 'buchi', 'sbacc') # Remove "alive" atomic propositions and print result. print(spot.to_finite(aut).to_str('hoa'))
HOA: v1 States: 4 Start: 2 AP: 3 "b" "a" "c" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc deterministic --BODY-- State: 0 [!2] 0 [2] 1 State: 1 {0} [t] 1 State: 2 [0&!2] 0 [0&2] 1 [!0&1&!2] 2 [!0&1&2] 3 State: 3 [0] 1 [!0&1] 3 --END--
If you need to print the automaton in a custom format (some finite automaton format probably), you should check our example of custom print of an automaton.
C++ version
The C++ version is straightforward adaptation of the Python version.
(The Python function translate()
is a convenient Python-only
wrappers around the spot::translator
object that we need to use
here.)
#include <iostream> #include <spot/tl/parse.hh> #include <spot/tl/ltlf.hh> #include <spot/twaalgos/translate.hh> #include <spot/twaalgos/hoa.hh> #include <spot/twaalgos/remprop.hh> int main() { spot::parsed_formula pf = spot::parse_infix_psl("(a U b) & Fc"); if (pf.format_errors(std::cerr)) return 1; spot::translator trans; trans.set_type(spot::postprocessor::Buchi); trans.set_pref(spot::postprocessor::SBAcc | spot::postprocessor::Small); spot::twa_graph_ptr aut = trans.run(spot::from_ltlf(pf.f)); aut = spot::to_finite(aut); print_hoa(std::cout, aut) << '\n'; return 0; }
HOA: v1 States: 4 Start: 2 AP: 3 "b" "a" "c" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc deterministic --BODY-- State: 0 [!2] 0 [2] 1 State: 1 {0} [t] 1 State: 2 [0&!2] 0 [0&2] 1 [!0&1&!2] 2 [!0&1&2] 3 State: 3 [0] 1 [!0&1] 3 --END--
Again, please check our example of custom print of an automaton if you need to print in some format for NFA/DFA.
Final notes
Spot only deals with infinite behaviors, so if you plan to use Spot to
perform some LTLf model checking, you should stop at step 3. Keep the
alive
proposition in your property automaton, and also add it to the
Kripke structure representing your system.
Alternatively, if your Kripke structure is already equipped with some
dead
property (as introduced by default in our ltsmin
interface),
you could replace alive
by !dead
by using ltlfilt
--from-ltl="!dead"
(from the command-line), a running
from_ltlf(f, "!dead")
in Python or C++.
When working with LTLf, there are two different semantics for the next operator:
- The weak next:
X a
is true ifa
hold in the next step, or if there is no next step. In particular,X(0)
is true iff there is no successor. (By the way, you cannot writeX0
because that is an atomic proposition: useX(0)
orX 0
.) - The strong next:
X[!] a
is true ifa
hold in the next step and there must be a next step. In particularX[!]1
asserts that there is a successor.
To see the difference between X
and X[!]
, consider the following translation
of (a & Xa) | (b & X[!]b)
:
Note that because in reality Spot supports only transitions-based acceptance, and the above automaton is really stored as a Büchi automaton that we decide to interpret as a finite automaton, there is a bit of trickery involved to represent an "accepting state" without any successor. While such a state makes sense in finite automata, it makes no sense in ω-automata: we fake it by adding a self-loop labeled by false (internally, this extra edge is carrying the acceptance mark that is displayed on the state). For instance: