UP | HOME

TLSF support

Table of Contents

Introduction to TLSF

The Temporal Logic Synthesis Format (or TLSF) is used to express synthesis problem in SYNTCOMP, the Reactive Synthesis competition.

Although the format is based on LTL it has a much more richer syntax, supporting array of variables (called buses) n-ary operators, parameters, helper functions, as well as the specification of the expected semantics and the partition of input and output variables. Version 1.2 of this format also adds support for finite-trace semantics.

There is a tool, called syfco, which can translate a TLSF specification to an LTLf or LTL formula in various syntaxes.

Some of Spot's command-line tools (ltlsynt, ltlfsynt, ltlf2dfa) have support for the TLSF format, but they all rely on syfco to translate TLSF to LTL or LTLf. You should install syfco somewhere on your $PATH for this to work.

Example TLSF specification

Here is an example TLSF file, chomp.tlsf, reprinted below.

INFO {
  TITLE:       "Chomp Game"
  DESCRIPTION: "Parameteric Chomp Game over a NxM grid"
  SEMANTICS:   Mealy,Finite
  TARGET:      Mealy
}

GLOBAL {
  PARAMETERS {
    N = 3;
    M = 3;
  }
  DEFINITIONS {
    Pos(grid, i, j) = grid[i + j * N];
    PickOne(sel) = ||[0 <= k < (SIZEOF sel)] sel[k];
    // if sel[i] is true, sel[i+1] should be too.
    AboveToo(sel) = &&[0 <= i < ((SIZEOF sel) - 1)] (sel[i] -> sel[i+1]);
    // if it's the other's turn, all values of sel should be false
    OtherTurn(other, sel) = other -> (&&[0 <= k < (SIZEOF sel)](!sel[k]));
    // if it's the other's turn and square (0,0) still exists, our
    // choices for sx and sy on next turn depend on what square is
    // available in the grid.
    NextChoices(grid, other, sx, sy) =
      (Pos(grid, 0, 0) && other) ->
        (||[0 <= x < N] ||[0 <= y < M] (Pos(grid, x, y) && X(sx[x] && sy[y])));
    // Rules that both players should obey
    PlayerRules(grid, other, sx, sy) =
       AboveToo(sx) &&
       AboveToo(sy) &&
       OtherTurn(other, sx) &&
       OtherTurn(other, sy) &&
       NextChoices(grid, other, sx, sy);
  }
}

MAIN {
  INPUTS {
    // When it's the input player's turn, they
    // use ix[I] iy[J] to select square (I,J).
    ix[N];
    iy[M];
  }
  OUTPUTS {
    // When it's the input player's turn, they
    // use ix[I] iy[J] to select square (I,J).
    ox[N];
    oy[M];
    // whether a square is available
    os[N*M];
    // turn
    oti; // input turn
    oto; // output turn
  }
  PRESET {
    // The output player is the first player.
    !oti && oto;
    // The output player has to choose a square.
    PickOne(ox);
    PickOne(oy);
    // The squares that are taken initially depend on the first choice.
    &&[0 <= i < N] &&[0 <= j < M] (Pos(os, i, j) <-> !(ox[i] && oy[j]));

    // Turns alternate between players.
    // (Writing G(oto <-> X oti) would be ok in LTL, but not LTLf.)
    G(oti <-> !oto);
    G(oto -> X oti);
    G(oti -> X oto);
  }
  REQUIRE {
    PlayerRules(os, oto, ix, iy);
  }
  ASSERT {
    PlayerRules(os, oti, ox, oy);
    // If (i,j) is selected by either player, that (i,j) should become false.
    &&[0 <= i < N] &&[0 <= j < M]
      (((ox[i] && oy[j]) || (ix[i] && iy[j])) -> !Pos(os,i,j));
    // if (i,j) is not availlable now, it's not available on next turn.
    &&[0 <= i < N] &&[0 <= j < M] ((!Pos(os,i,j)) -> X !(Pos(os,i,j)));
    // if (i,j) is availlable now, it might not be on next turn.
    &&[0 <= i < N] &&[0 <= j < M]
      (Pos(os,i,j) -> X(Pos(os,i,j) || (ox[i] && oy[j]) || (ix[i] && iy[j])));
  }
  GUARANTEE {
    // The game finishes once square (0, 0) has been taken, and since
    // this square is poisonous, we want the input player to take it.
    Pos(os,0,0) U (!Pos(os,0,0) && oti);
  }
}

This describes a specification for the "chomp" two-players game. One player (called input) is played by the environment, the other player (output) is played by the controller. The game is run over a chocolate bar of N*M squares. The output player plays first, and alternate turns with the other input player. At their turn, the players select an available chocolate square at coordinate \((i,j)\), and eat all other chocolate squares \(\{(x,y)\mid x\ge i \land y\ge j\}\). The players who takes the last square at position \((0,0)\) loses.

The above specification asks if there exists a winning strategy for the output player. Such a strategy is know to exists for any \((N,M)\ne(1,1)\).

This can be verified with ltlfsynt quite easily:

ltlfsynt --tlsf chomp.tlsf --realizability
REALIZABLE

Removing the --realizability flag would output the strategy, but because that specification uses 23 atomic propositions, the resulting strategy is not very readable.

How is syfco used

Running the previous command with the --verbose option will show that ltlfsynt actually perform three calls to syfco.

ltlfsynt --tlsf chomp.tlsf --realizability --verbose 2>&1 | grep syfco
running: syfco -f ltlxba-fin -m fully chomp.tlsf
running: syfco --print-output-signals chomp.tlsf
running: syfco --print-target chomp.tlsf

The first command converts the TLSF specification into LTLf. -f ltlxba-fin requests a syntax of LTLf that is compatible with Spot, and -m fully requests fully parenthesized formulas to avoid ambiguities in operator priorities. Running that first command on this example already produces a pretty large formula:

syfco -f ltlxba-fin -m fully chomp.tlsf | fmt -80
(((((((((((((((((! (oti)) && (oto)) && (((ox_0) || (ox_1)) || (ox_2))) &&
(((oy_0) || (oy_1)) || (oy_2))) && ((os_0) <-> (! ((ox_0) && (oy_0))))) &&
((os_3) <-> (! ((ox_0) && (oy_1))))) && ((os_6) <-> (! ((ox_0) && (oy_2))))) &&
((os_1) <-> (! ((ox_1) && (oy_0))))) && ((os_4) <-> (! ((ox_1) && (oy_1))))) &&
((os_7) <-> (! ((ox_1) && (oy_2))))) && ((os_2) <-> (! ((ox_2) && (oy_0))))) &&
((os_5) <-> (! ((ox_2) && (oy_1))))) && ((os_8) <-> (! ((ox_2) && (oy_2)))))
&& (G ((oti) <-> (! (oto))))) && (G ((oto) -> (X (oti))))) && (G ((oti) -> (X
(oto))))) && ((G ((((((((ix_0) -> (ix_1)) && ((ix_1) -> (ix_2))) && ((iy_0) ->
(iy_1))) && ((iy_1) -> (iy_2))) && ((oto) -> (((! (ix_0)) && (! (ix_1))) &&
(! (ix_2))))) && ((oto) -> (((! (iy_0)) && (! (iy_1))) && (! (iy_2))))) &&
(((os_0) && (oto)) -> ((((((os_0) && (X ((ix_0) && (iy_0)))) || ((os_3) && (X
((ix_0) && (iy_1))))) || ((os_6) && (X ((ix_0) && (iy_2))))) || ((((os_1) &&
(X ((ix_1) && (iy_0)))) || ((os_4) && (X ((ix_1) && (iy_1))))) || ((os_7) &&
(X ((ix_1) && (iy_2)))))) || ((((os_2) && (X ((ix_2) && (iy_0)))) || ((os_5)
&& (X ((ix_2) && (iy_1))))) || ((os_8) && (X ((ix_2) && (iy_2))))))))) -> ((G
(((((((((((((((((((((((((((((((((((ox_0) -> (ox_1)) && ((ox_1) -> (ox_2)))
&& ((oy_0) -> (oy_1))) && ((oy_1) -> (oy_2))) && ((oti) -> (((! (ox_0))
&& (! (ox_1))) && (! (ox_2))))) && ((oti) -> (((! (oy_0)) && (! (oy_1)))
&& (! (oy_2))))) && (((os_0) && (oti)) -> ((((((os_0) && (X ((ox_0) &&
(oy_0)))) || ((os_3) && (X ((ox_0) && (oy_1))))) || ((os_6) && (X ((ox_0) &&
(oy_2))))) || ((((os_1) && (X ((ox_1) && (oy_0)))) || ((os_4) && (X ((ox_1)
&& (oy_1))))) || ((os_7) && (X ((ox_1) && (oy_2)))))) || ((((os_2) && (X
((ox_2) && (oy_0)))) || ((os_5) && (X ((ox_2) && (oy_1))))) || ((os_8) &&
(X ((ox_2) && (oy_2)))))))) && ((((ox_0) && (oy_0)) || ((ix_0) && (iy_0))) ->
(! (os_0)))) && ((((ox_0) && (oy_1)) || ((ix_0) && (iy_1))) -> (! (os_3)))) &&
((((ox_0) && (oy_2)) || ((ix_0) && (iy_2))) -> (! (os_6)))) && ((((ox_1) &&
(oy_0)) || ((ix_1) && (iy_0))) -> (! (os_1)))) && ((((ox_1) && (oy_1)) ||
((ix_1) && (iy_1))) -> (! (os_4)))) && ((((ox_1) && (oy_2)) || ((ix_1) &&
(iy_2))) -> (! (os_7)))) && ((((ox_2) && (oy_0)) || ((ix_2) && (iy_0))) ->
(! (os_2)))) && ((((ox_2) && (oy_1)) || ((ix_2) && (iy_1))) -> (! (os_5)))) &&
((((ox_2) && (oy_2)) || ((ix_2) && (iy_2))) -> (! (os_8)))) && ((! (os_0))
-> (X (! (os_0))))) && ((! (os_3)) -> (X (! (os_3))))) && ((! (os_6)) ->
(X (! (os_6))))) && ((! (os_1)) -> (X (! (os_1))))) && ((! (os_4)) ->
(X (! (os_4))))) && ((! (os_7)) -> (X (! (os_7))))) && ((! (os_2)) ->
(X (! (os_2))))) && ((! (os_5)) -> (X (! (os_5))))) && ((! (os_8)) -> (X
(! (os_8))))) && ((os_0) -> (X (((os_0) || ((ox_0) && (oy_0))) || ((ix_0) &&
(iy_0)))))) && ((os_3) -> (X (((os_3) || ((ox_0) && (oy_1))) || ((ix_0) &&
(iy_1)))))) && ((os_6) -> (X (((os_6) || ((ox_0) && (oy_2))) || ((ix_0) &&
(iy_2)))))) && ((os_1) -> (X (((os_1) || ((ox_1) && (oy_0))) || ((ix_1) &&
(iy_0)))))) && ((os_4) -> (X (((os_4) || ((ox_1) && (oy_1))) || ((ix_1) &&
(iy_1)))))) && ((os_7) -> (X (((os_7) || ((ox_1) && (oy_2))) || ((ix_1) &&
(iy_2)))))) && ((os_2) -> (X (((os_2) || ((ox_2) && (oy_0))) || ((ix_2) &&
(iy_0)))))) && ((os_5) -> (X (((os_5) || ((ox_2) && (oy_1))) || ((ix_2) &&
(iy_1)))))) && ((os_8) -> (X (((os_8) || ((ox_2) && (oy_2))) || ((ix_2) &&
(iy_2))))))) && ((os_0) U ((! (os_0)) && (oti))))))

The second command simply extracts the list of output propositions. In this case the authors of the TLSF file used the convention that every output proposition starts with o, but that is not mandatory.

syfco --print-output-signals chomp.tlsf
oto, oti, os_0, os_1, os_2, os_3, os_4, os_5, os_6, os_7, os_8, oy_0, oy_1, oy_2, ox_0, ox_1, ox_2

There is now query for the list of input propositions: any proposition that appear in the formula but that is not an output proposition is assumed to be an input proposition.

Finally the last call to syfco is used to retrieve the semantics that should be used to check for realizability. In this case this declared as Mealy: the output player can see the choices of the input player at the current turn before making his decision.

syfco --print-target chomp.tlsf
Mealy

Note that you can use --semantics, --ins, or --outs to override the partition of propositions or the semantics. In this case, ltlfsynt will not make unnecesary calls syfco.

ltlfsynt --tlsf chomp.tlsf --semantics=Moore --outs='/^o/' --realizability \
  --verbose 2>&1 | grep 'syfco\|REAL'
running: syfco -f ltlxba-fin -m fully chomp.tlsf
UNREALIZABLE

When --tlsf is not enough

If you do not have syfco installed in your $PATH, or want to replace syfco, or want to TLSF with a tool that does not support the --tlsf option, you can still call that tool explicitly and pass the LTL/LTLf formulas directly.

Beware that doing something like this only works up to a certain size of specifications:

LTL=$(syfco -f ltlxba-fin -m fully chomp.tlsf)  # <- bad idea!
OUT=$(syfco --print-output-signals chomp.tlsf)
SEM=$(syfco --print-target chomp.tlsf)
ltlfsynt --formula="$LTL" --outs="$OUT" --semantics="$SEM" --realizability
REALIZABLE

At some point the converted LTL/LTLf formula becomes to big to be passed on the command line. It is better to pass the formula using a temporary file or via using a pipe as in the following (-F - will read the formula from standard input).

OUT=$(syfco --print-output-signals chomp.tlsf)
SEM=$(syfco --print-target chomp.tlsf)
syfco -f ltlxba-fin -m fully chomp.tlsf |
  ltlfsynt -F - --outs="$OUT" --semantics="$SEM" --realizability
REALIZABLE

(When option --tlsf is used, everything that syfco outputs is received through a pipe.)

Overwriting TLSF parameters

Some TLSF models, like chomp.tlsf, are defined with parameters. Those parameters have default values specified within the PARAMETERS block of the TLSF specification. You may overwrite the value of those parameters by appending /PARAM1=VAL1,PARAM2=VAL2... to the filename that is passed to --tlsf.

We can use that to verify that M=1,N=1 is not realizable since the first player is forced to take the only square.

ltlfsynt --tlsf chomp.tlsf/N=1,M=1 --realizability --verbose 2>&1 |
  grep 'syfco\|REAL'
running: syfco -op N=1 -op M=1 -f ltlxba-fin -m fully chomp.tlsf
running: syfco -op N=1 -op M=1 --print-output-signals chomp.tlsf
running: syfco --print-target chomp.tlsf
UNREALIZABLE