Commit 16a8c031 authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz

ltldo: new binary

* src/bin/, src/bin/common_trans.hh: New files,
extracted from...
* src/bin/ ... here, so that ltldo can use them.
* src/bin/ New file.
* src/bin/ Adjust.
* src/bin/, src/bin/common_aoutput.hh: Make
it possible to add new statistics.
* doc/org/ New file.
* doc/, doc/org/ Adjust.
* src/bin/man/ltldo.x: New file.
* src/bin/man/ Adjust.
* src/bin/man/ltlcross.x, src/bin/man/ltlfilt.x: Mention ltldo(1).
* src/tgbatest/ltldo.test, src/tgbatest/ltldo2.test: New files.
* src/tgbatest/ Add them.
* NEWS: Mention ltldo.
parent e5294aac
......@@ -20,6 +20,10 @@ New in spot 1.99a (not yet released)
- autfilt is a new tool that converts/filters/transforms a
stream of automata.
- ltldo is a new tool that run LTL/PSL formulas through other
tools, but uses Spot's command-line interfaces for specifying
input and output.
- "ltlfilt --stutter-invariant" will now work with PSL formula.
The implementation is actually much more efficient
than the previous implementation that was only for LTL.
......@@ -69,6 +69,7 @@ ORG_FILES = \
org/ \
org/ \
org/ \
org/ \
org/ \
org/ \
org/ \
......@@ -11,3 +11,4 @@ gfagfb
# -*- coding: utf-8 -*-
#+TITLE: =ltldo=
#+HTML_LINK_UP: tools.html
This tool is a wrapper for tools that read LTL/PSL formulas and
(optionally) output automata.
It reads formulas specified using the [[][common options for specifying
input]] and passes each formula to a tool (or a list of tools) specified
using options similar to those of [[][=ltlcross=]]. In case that tool
returns an automaton, the resulting automaton is read back by =ltldo=
and is finally output as specified using the [[][common options for
outputing automata]].
In effect, =ltldo= wraps the I/O interface of the Spot tools on top of
any other tool.
* Example: computing statistics for =ltl3ba=
As a motivating example, consider a scenario where we want to run
[[][=ltl3ba=]] on a set of 10 formulas stored in a file. For each formula
we would like to compute compute the number of states and edges in the
Büchi automaton produced by =ltl3ba=.
Here is the input file:
#+BEGIN_SRC sh :results verbatim :exports code
cat >sample.ltl <<EOF
1 U a
!(!((a U Gb) U b) U GFa)
(b <-> Xc) xor Fb
FXb R (a R (1 U b))
G(!(c | (a & (a W Gb))) M Xa)
GF((b R !a) U (Xc M 1))
G(Xb | Gc)
XG!F(a xor Gb)
We will first implement this scenario without =ltldo=.
A first problem that the input is not in the correct syntax: although
=ltl3ba= understands =G= and =F=, it does not support =xor= or =M=,
and requires the Boolean operators =||= and =&&=. This syntax
issue can be fixed by processing the input with [[][=ltlfilt -s=]].
A second problem is that =ltl3ba= (at least version 1.1.1) can only
process one formula at a time. So we'll need to call =ltl3ba= in a
Finally, one way to compute the size of the resulting Büchi automaton
is to pipe the output of =ltl3ba= through [[][=autfilt=]].
Here is how the shell command could look like:
#+BEGIN_SRC sh :results verbatim :exports both
ltlfilt -F sample.ltl -s |
while read f; do
ltl3ba -f "$f" | autfilt --stats="$f,%s,%t"
true U a,2,4
!(!((a U []b) U b) U []<>a),2,4
(((!b && !Xc) || (b && Xc)) && !<>b) || (<>b && !((!b && !Xc) || (b && Xc))),7,21
<>Xb V (a V (true U b)),6,28
[](Xa U (Xa && !(c || (a && ([]b V (a || []b)))))),1,0
[]<>((b V !a) U (true U Xc)),2,4
[](Xb || []c),3,11
X[]!<>((a && ![]b) || (!a && []b)),4,10
Using =ltldo= the above command can be reduced to this:
#+BEGIN_SRC sh :results verbatim :exports both
ltldo -F sample.ltl 'ltl3ba -f %s>%N' --stats='%f,%s,%t'
1 U a,2,4
!(!((a U Gb) U b) U GFa),2,4
(b <-> Xc) xor Fb,7,21
FXb R (a R (1 U b)),6,28
G(!(c | (a & (a W Gb))) M Xa),1,0
GF((b R !a) U (Xc M 1)),2,4
G(Xb | Gc),3,11
XG!F(a xor Gb),4,10
Note that the formulas look different in both cases, because in the
=while= loop the formula printed has already been processed with
=ltlfilt=, while =ltldo= emits the input string untouched.
* Example: running =spin= and producing HOA
Here is another example, where we use Spin to produce two automata in
the [[][HOA format]]. Spin has no support for HOA, but =ltldo= simply
converts the never claim produced by =spin= into this format.
#+BEGIN_SRC sh :results verbatim :exports both
ltldo -f a -f GFa 'spin -f %s>%N' -H
HOA: v1
States: 2
Start: 0
AP: 1 "a"
acc-name: Buchi
Acceptance: 1 Inf(0)
properties: trans-labels explicit-labels state-acc deterministic
State: 0 {0}
[0] 1
State: 1 {0}
[t] 1
HOA: v1
States: 2
Start: 0
AP: 1 "a"
acc-name: Buchi
Acceptance: 1 Inf(0)
properties: trans-labels explicit-labels state-acc complete
State: 0
[0] 1
[t] 0
State: 1 {0}
[t] 0
* Syntax for specifying tools to call
The syntax for specifying how a tool should be called is the same as
in [[][=ltlcross=]]. Namely, the following sequences are available.
#+BEGIN_SRC sh :results verbatim :exports results
ltldo --help | sed -n '/character sequences:/,/^$/p' | sed '1d;$d'
: %f,%s,%l,%w the formula as a (quoted) string in Spot, Spin,
: LBT, or Wring's syntax
: %F,%S,%L,%W the formula as a file in Spot, Spin, LBT, or
: Wring's syntax
: %N,%T,%D,%H the automaton is output as a Never claim, or in
: LBTT's, in LTL2DSTAR's, or in the HOA format
Contrarily to =ltlcross=, it this not mandatory to specify an output filename
using one of the sequence for that later lines. For instance we could
simply run a formula though =echo= to compare different output syntaxes:
#+BEGIN_SRC sh :results verbatim :exports both
ltldo -f 'p0 U p1' -f 'GFp0' 'echo %f, %s, %l, %w'
: (p0) U (p1), (p0) U (p1), U p0 p1, (p0=1) U (p1=1)
: (G(F(p0))), ([](<>(p0))), G F p0, (G(F(p0=1)))
In this case (i.e., when the command does not specify any output
filename), =ltldo= will not output anything.
As will =ltlcross=, multiple commands can be given, and they will be
executed on each formula in the same order.
A typical use-case is to compare statistics of different tools:
#+BEGIN_SRC sh :results verbatim :exports both
ltldo -F sample.ltl 'spin -f %s>%N' 'ltl3ba -f %s>%N' --stats=%T,%f,%s,%e
spin -f %s>%N,1,2,2
ltl3ba -f %s>%N,1,1,1
spin -f %s>%N,1 U a,2,3
ltl3ba -f %s>%N,1 U a,2,3
spin -f %s>%N,!(!((a U Gb) U b) U GFa),23,86
ltl3ba -f %s>%N,!(!((a U Gb) U b) U GFa),2,3
spin -f %s>%N,(b <-> Xc) xor Fb,12,23
ltl3ba -f %s>%N,(b <-> Xc) xor Fb,7,11
spin -f %s>%N,FXb R (a R (1 U b)),28,176
ltl3ba -f %s>%N,FXb R (a R (1 U b)),6,20
spin -f %s>%N,Ga,1,1
ltl3ba -f %s>%N,Ga,1,1
spin -f %s>%N,G(!(c | (a & (a W Gb))) M Xa),15,51
ltl3ba -f %s>%N,G(!(c | (a & (a W Gb))) M Xa),1,0
spin -f %s>%N,GF((b R !a) U (Xc M 1)),12,60
ltl3ba -f %s>%N,GF((b R !a) U (Xc M 1)),2,4
spin -f %s>%N,G(Xb | Gc),4,8
ltl3ba -f %s>%N,G(Xb | Gc),3,5
spin -f %s>%N,XG!F(a xor Gb),8,21
ltl3ba -f %s>%N,XG!F(a xor Gb),4,7
Here we used =%T= to output the name of the tool used to translate the
formula =%f= as an automaton with =%s= states and =%e= edges.
If you feel that =%T= has too much clutter, you can give each tool
a shorter name by prefixing its command with ={name}=.
In the following example, we moved the formula used on its own line
using the trick that the command =echo %f= will not be subject to
=--stats= (since it does not declare any output automaton).
#+BEGIN_SRC sh :results verbatim :exports both
ltldo -F sample.ltl --stats=%T,%s,%e \
'echo "#" %f' '{spin}spin -f %s>%N' '{ltl3ba}ltl3ba -f %s>%N'
# (1)
# (1) U (a)
# (!((!(((a) U (G(b))) U (b))) U (G(F(a)))))
# ((b) <-> (X(c))) xor (F(b))
# (F(X(b))) R ((a) R ((1) U (b)))
# (G(a))
# (G((!((c) | ((a) & ((a) W (G(b)))))) M (X(a))))
# (G(F(((b) R (!(a))) U ((X(c)) M (1)))))
# (G((X(b)) | (G(c))))
# (X(G(!(F((a) xor (G(b)))))))
Much more readable!
* Controlling and measuring time
The run time of each command can be restricted with the =-T NUM=
option. The argument is the maximum number of seconds that each
command is allowed to run.
When a timeout occurs a warning is printed on stderr, and no automaton
(or statistic) is output by =ltdo= for this specific pair of
command/formula. The processing then continue with other formulas and
tools. Timeouts are not considered as errors, so they have no effect
on the exit status of =ltldo=.
For each command (that does not terminate with a timeout) the runtime
can be printed using the =%r= escape sequence. This makes =ltldo= an
alternative to [[][=ltlcross=]] for running benchmarks without any
......@@ -48,6 +48,8 @@ corresponding commands are hidden.
Büchi automata.
- [[][=randaut=]] Generate random automata.
- [[][=autfilt=]] Filter and convert automata.
- [[][=ltldo=]] Run LTL/PSL formulas through other tools using common [[][input]]
and [[][output]] interfaces.
* Advanced use-cases
......@@ -4,6 +4,7 @@ genltl
......@@ -44,10 +44,22 @@ libcommon_a_SOURCES = \
common_r.hh \ \
common_setup.hh \
common_sys.hh \ \
bin_PROGRAMS = autfilt ltlfilt genltl randaut randltl ltl2tgba \
ltl2tgta ltlcross dstar2tgba ltlgrind
bin_PROGRAMS = \
autfilt \
dstar2tgba \
genltl \
ltl2tgba \
ltl2tgta \
ltlcross \
ltldo \
ltlfilt \
ltlgrind \
randaut \
# Dummy program used just to generate man/spot-x.7 in a way that is
# consistent with the other man pages (e.g., with a version number that
......@@ -63,6 +75,7 @@ ltl2tgba_SOURCES =
ltl2tgta_SOURCES =
ltlcross_SOURCES =
ltlgrind_SOURCES =
ltldo_SOURCES =
dstar2tgba_SOURCES =
spot_x_SOURCES =
......@@ -23,6 +23,7 @@
#include "common_aoutput.hh"
#include "common_post.hh"
#include "common_cout.hh"
#include "common_post.hh"
#include "tgba/bddprint.hh"
......@@ -259,3 +260,9 @@ automaton_printer::print(const spot::tgba_digraph_ptr& aut,
void automaton_printer::add_stat(char c, const spot::printable* p)
namer.declare(c, p);
statistics.declare(c, p);
......@@ -97,6 +97,8 @@ public:
declare('w', &aut_word_);
using spot::formater::declare;
/// \brief print the configured statistics.
/// The \a f argument is not needed if the Formula does not need
......@@ -218,6 +220,8 @@ public:
// Time and input automaton for statistics
double time = 0.0,
const spot::const_hoa_aut_ptr& haut = nullptr);
void add_stat(char c, const spot::printable* p);
// -*- coding: utf-8 -*-
// Copyright (C) 2015 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
// This file is part of Spot, a model checking library.
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <>.
#include "common_trans.hh"
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include "error.h"
#include "ltlvisit/tostring.hh"
#include "ltlvisit/lbt.hh"
#include "common_conv.hh"
translator_spec::translator_spec(const char* spec)
: spec(spec), cmd(spec), name(spec)
if (*cmd != '{')
// Match the closing '}'
const char* pos = cmd;
unsigned count = 1;
while (*++pos)
if (*pos == '{')
else if (*pos == '}')
if (!--count)
name = strndup(cmd + 1, pos - cmd - 1);
cmd = pos + 1;
while (*cmd == ' ' || *cmd == '\t')
translator_spec::translator_spec(const translator_spec& other)
: spec(other.spec), cmd(other.cmd), name(
if (name != spec)
name = strdup(name);
if (name != spec)
std::vector<translator_spec> translators;
quoted_string::print(std::ostream& os, const char* pos) const
os << '\'';
this->spot::printable_value<std::string>::print(os, pos);
os << '\'';
val_ = 0;
delete val_;
void printable_result_filename::reset(unsigned n)
translator_num = n;
format = None;
void printable_result_filename::cleanup()
delete val_;
val_ = 0;
printable_result_filename::print(std::ostream& os, const char* pos) const
output_format old_format = format;
if (*pos == 'N')
format = Hoa; // The HOA parse also reads neverclaims
else if (*pos == 'T')
format = Lbtt;
else if (*pos == 'D')
format = Dstar;
else if (*pos == 'H')
format = Hoa;
if (val_)
// It's OK to use a specifier multiple time, but it's not OK
// to mix the formats.
if (format != old_format)
error(2, 0,
"you may not mix %%D, %%H, %%N, and %%T specifiers: %s",
char prefix[30];
snprintf(prefix, sizeof prefix, "lcr-o%u-", translator_num);
= spot::create_tmpfile(prefix);
os << '\'' << val_ << '\'';
translator_runner::translator_runner(spot::bdd_dict_ptr dict,
bool no_output_allowed)
: dict(dict)
declare('f', &string_ltl_spot);
declare('s', &string_ltl_spin);
declare('l', &string_ltl_lbt);
declare('w', &string_ltl_wring);
declare('F', &filename_ltl_spot);
declare('S', &filename_ltl_spin);
declare('L', &filename_ltl_lbt);
declare('W', &filename_ltl_wring);
declare('D', &output);
declare('H', &output);
declare('N', &output);
declare('T', &output);
size_t s = translators.size();
for (size_t n = 0; n < s; ++n)
// Check that each translator uses at least one input and
// one output.
std::vector<bool> has(256);
const translator_spec& t = translators[n];
scan(t.cmd, has);
if (!(has['f'] || has['s'] || has['l'] || has['w']
|| has['F'] || has['S'] || has['L'] || has['W']))
error(2, 0, "no input %%-sequence in '%s'.\n Use "
"one of %%f,%%s,%%l,%%w,%%F,%%S,%%L,%%W to indicate how "
"to pass the formula.", t.spec);
if (!no_output_allowed
&& !(has['D'] || has['N'] || has['T'] || has['H']))
error(2, 0, "no output %%-sequence in '%s'.\n Use one of "
"%%D,%%H,%%N,%%T to indicate where the automaton is saved.",
// Remember the %-sequences used by all translators.
translator_runner::string_to_tmp(std::string& str, unsigned n,
std::string& tmpname)
char prefix[30];
snprintf(prefix, sizeof prefix, "lcr-i%u-", n);
spot::open_temporary_file* tmpfile = spot::create_open_tmpfile(prefix);
tmpname = tmpfile->name();
int fd = tmpfile->fd();
ssize_t s = str.size();
if (write(fd, str.c_str(), s) != s
|| write(fd, "\n", 1) != 1)
error(2, errno, "failed to write into %s", tmpname.c_str());
const std::string&
translator_runner::formula() const
// Pick the most readable format we have...
if (!string_ltl_spot.val().empty())
return string_ltl_spot;
if (!string_ltl_spin.val().empty())
return string_ltl_spin;
if (!string_ltl_wring.val().empty())
return string_ltl_wring;
if (!string_ltl_lbt.val().empty())
return string_ltl_lbt;
return string_ltl_spot;
translator_runner::round_formula(const spot::ltl::formula* f, unsigned serial)
if (has('f') || has('F'))
string_ltl_spot = spot::ltl::to_string(f, true);
if (has('s') || has('S'))
string_ltl_spin = spot::ltl::to_spin_string(f, true);
if (has('l') || has('L'))
string_ltl_lbt = spot::ltl::to_lbt_string(f);
if (has('w') || has('W'))
string_ltl_wring = spot::ltl::to_wring_string(f);
if (has('F'))
string_to_tmp(string_ltl_spot, serial, filename_ltl_spot);
if (has('S'))
string_to_tmp(string_ltl_spin, serial, filename_ltl_spin);
if (has('L'))
string_to_tmp(string_ltl_lbt, serial, filename_ltl_lbt);
if (has('W'))
string_to_tmp(string_ltl_wring, serial, filename_ltl_wring);
volatile bool timed_out = false;
unsigned timeout_count = 0;
static unsigned timeout = 0;
static volatile int alarm_on = 0;
static int child_pid = -1;
static void
sig_handler(int sig)
if (child_pid == 0)