UP | HOME

# TLSF support

## Table of Contents

- Introduction to TLSF
- Example TLSF specification
- How is syfco used
- When `--tlsf` is not enough
- Overwriting TLSF parameters

## 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