# -*- coding: utf-8 -*- #+TITLE: =ltlgrind= #+DESCRIPTION: Spot command-line tool for combining LTL formulas randomly #+INCLUDE: setup.org #+HTML_LINK_UP: tools.html #+PROPERTY: header-args:sh :results verbatim :exports both This tool creates new formulas by combining formulas randomly selected from an input set of formulas. Some authors have argued that for some tasks, like [[https://www.cs.rice.edu/~vardi/papers/time13.pdf][LTL satisfiability]], working with randomly generated formulas is often easy, because random formulas tend to simplify trivially. =ltlmix= allows you to take a set of formulas, usually some handwritten, meaningful formulas, and combine those formulas to build larger sets that are possibly more challenging. Here is a very simple example that builds five formulas that are Boolean combination of formulas from taken in the set $\{\mathsf{GF}a,\mathsf{FG}b,\mathsf{X}c\}$: #+BEGIN_SRC sh :exports both ltlmix -f GFa -f FGb -f Xc -n 5 #+END_SRC #+RESULTS: : !FGb xor !Xc : !GFa -> !FGb : FGb | (FGb -> Xc) : FGb : GFa & (FGb | Xc) * Shape of the generated formulas ** Size of the syntax tree For each formula that it generates, =ltlmix= constructs a random syntax-tree of a certain size (5 by default) in which internal nodes represent operators selected randomly from a list of operator, and leaves are subformulas selected randomly from the set of input formulas. As an example, the syntax tree of =!φ₁ xor !φ₂= has size 5, and its leaves =φ₁= and =φ₂= will be taken randomly from the set of input formulas. The algorithm is actually the same as for =randltl=, except that =randltl= use random atomic propositions as leaves when =ltlmix= uses random formulas. The same input formula can be picked several times to be used on multiple leaves of the tree. Note that because Spot implements some trivial rewritings directly during the construction of any formula, formulas like =FGb | !!FGb= (which correspond to a tree of size 5 in the above example) cannot be represented: they are automatically simplified to =FGb=. Similarly, something like =φ xor φ= will be output as =0=. The size of the tree can be changed using the =--tree-size= option. #+BEGIN_SRC sh :exports both for i in 1 2 3 4 5 6 7 8 9 10 11 12; do ltlmix -fXa -fGb -fFc -fXd -fGe --tree-size=$i done #+END_SRC #+RESULTS: #+begin_example Fc !Xd 0 Ge xor !Fc !Xd xor !Ge !Xd xor (Fc | Xd) !Ge Ge xor (!Ge -> Gb) Ge xor (!Xa -> !Fc) (Ge & !Fc) xor (!Gb xor !Ge) (Ge & !Fc) xor (!Gb xor (Gb | Fc)) (Ge & (Gb xor Xd)) xor (!Fc -> (Gb | Fc)) #+end_example The above shows that while the size of the syntax tree generally grows along with =--tree-size= there are several cases where it reduces trivially. ** Operator priorities It is possible to control the set of operators used in the generation. The first step is to obtain that list of operators with =--dump-priorities=. For instance: #+BEGIN_SRC sh :exports both ltlmix -fXa -fGb -fFc -fXd -fGe --dump-priorities #+END_SRC #+RESULTS: #+begin_example Use --boolean-priorities to set the following Boolean formula priorities: sub 5 false 0 true 0 not 1 equiv 1 implies 1 xor 1 and 1 or 1 #+end_example In the above list, =false= and =true= represent the Boolean constants (which are usually undesirable when building random Boolean formulas), and =sub= represent a random formula drawn from the list of input formulas. The above command shows that each operator has a weight, called /priority/. When the priority is 0, the operator is never used. When =ltlmix= generates a syntax tree of size N, it looks among all operators that can be used at the root of such a tree, and performs a weighted random selection. In other words, an operator with priority =2= will be twice more likely to be selected than an operator with priority =1=. Those priorities can be changed with =--boolean-priorities= as in the following example, which disables =xor= and makes =<->= thrice more likely to appear. #+BEGIN_SRC sh :exports both for i in 1 2 3 4 5 6 7 8 9 10 11 12; do ltlmix -fXa -fGb -fFc -fXd -fGe --tree-size=$i --boolean-prio='xor=0,equiv=3' done #+END_SRC #+RESULTS: #+begin_example Fc !Xd 1 Ge <-> !Fc !Xd <-> !Ge !Xd <-> (Fc | Xd) Ge Ge <-> (Gb <-> !Ge) Ge <-> (!Fc <-> !Xa) (Ge & !Fc) <-> (!Ge -> !Gb) (Ge & !Fc) <-> ((Gb | Fc) -> !Gb) (Ge & (Gb <-> Xd)) <-> ((Gb | Fc) <-> !Fc) #+end_example ** Boolean or LTL syntax tree By default, the syntax tree generated on top of the randomly selected input formula uses only Boolean operators. Using option =-L= will use LTL operators instead. #+BEGIN_SRC sh :exports both ltlmix -fXa -fGb -fFc --tree-size=10 -L -n10 #+END_SRC #+RESULTS: #+begin_example Gb R (XFc W 0) !(Gb | !Xa) 1 U !X(0) (Xa xor Gb) -> (GXa M Fc) GFc M 1 (Fc U Gb) -> (0 R Xa) !Gb <-> (Gb | GFc) 1 GFc | (1 U Xa) !(Xa | GFc) #+end_example The following operators are used: #+BEGIN_SRC sh :exports both ltlmix -fXa -fGb -fFc -fXd -fGe -L --dump-priorities #+END_SRC #+RESULTS: #+begin_example Use --ltl-priorities to set the following LTL priorities: sub 5 false 1 true 1 not 1 F 1 G 1 X 1 equiv 1 implies 1 xor 1 R 1 U 1 W 1 M 1 and 1 or 1 #+end_example Note that in the LTL case, =false= and =true= can be generated by default. * Randomizing atomic propositions with =-A= or =-P= Options =-A= or =-P= can be used to change the atomic propositions used in the input formulas. This works as follows: if =-A N= was given, every time an input formula φ is selected, its atomic propositions are replaced by atomic propositions randomly selected in a set of size $N$. If φ uses $i$ atomic propositions and $i\ge N$, then those $i$ atomic proposition will be remapped to $i$ distinct atomic propositions chosen randomly in that set. if $i>N$, some of the new atomic propositions may replace several of the original atomic propositions. Option =-P N= is similar to =-A N= except that the selected atomic propositions can possibly be negated. These options solve two problems: - They lessen the issue that a formula selected several times can lead to syntax tree such as =φ | φ | φ= that reduces to =φ=. Now, each occurrence of =φ= as a chance to use different atomic propositions. (the larger =N= is, the more likely it is that these copies of φ will be different). - They allow combining formulas that had completely different sets of atomic propositions, in such a way that they are now interdependent (the smaller N is the more likely it is that subformulas will share atomic propositions). Here is an example with a single formula, =GFa=, whose atomic proposition will be randomly replaced by one of $\{p_0,p_1,p_2,p_3,p_4\}$. #+BEGIN_SRC sh :exports both ltlmix -fGFa -A5 --tree-size=8 -n10 #+END_SRC #+RESULTS: #+begin_example (GFp2 & GFp3) xor (!GFp0 xor GFp1) (GFp4 -> GFp1) -> !GFp2 !GFp4 | ((GFp2 & GFp3) -> GFp2) !GFp2 | (GFp3 <-> (GFp2 & GFp1)) !GFp0 | GFp4 !(GFp2 & GFp1) <-> (GFp3 xor GFp0) (GFp2 xor GFp0) | (GFp4 -> !GFp0) (GFp4 | !GFp3) -> GFp4 !GFp0 -> (GFp2 | GFp1) !GFp1 <-> (!GFp2 xor !GFp1) #+end_example Here is a similar example, with polarized atomic propositions instead: #+BEGIN_SRC sh :exports both ltlmix -fGFa -P5 --tree-size=8 -n10 #+END_SRC #+RESULTS: #+begin_example (GFp2 & GF!p3) xor (GFp4 -> !GF!p1) (GFp4 | !GFp2) -> (GFp1 -> GF!p1) !GF!p2 & (GF!p0 xor (GF!p0 -> GF!p3)) GFp1 <-> (GF!p3 | !GFp0) GF!p1 & GFp0 & (GF!p3 xor !GF!p4) (GF!p1 xor GF!p2) | (GF!p3 & !GF!p4) !(GFp4 xor (!GF!p2 | !GF!p3)) GFp0 | (!GFp1 -> (GFp1 -> GF!p4)) GF!p1 xor (!GF!p2 | (GF!p1 <-> GFp0)) !((GF!p2 <-> GF!p4) & (GFp1 xor GF!p2)) #+end_example * More serious examples ** Mixing the DAC patterns The command [[file:genltl.org][=genltl --dac-pattern=]] will print a list of 55 LTL formulas representing various specification patterns listed by Dwyer et al. (FMSP'98). Using =--stat=%x= to count the atomic propositions in each formula, and some standard unix tools, we can compute that they use at most 6 atomic propositions. #+BEGIN_SRC sh :exports both genltl --dac-pattern --stat=%x | sort -n | tail -n 1 #+END_SRC #+RESULTS: : 6 Based on this, we could decide to generate Boolean combination of those formulas while replacing atomic propositions by literals built out of a set of 10 atomic propositions (chosen larger than 6 to ensure that each individual formula will still make sense after the change of atomic propositions). #+BEGIN_SRC sh :exports both genltl --dac-pattern | ltlmix -n8 -P10 #+END_SRC #+RESULTS: : !G((p8 & F!p7) -> (!p4 U (!p7 | (!p2 & !p4 & X(!p4 U p1))))) xor !G(!p3 -> ((p4 & !p7) U (!p4 | ((p4 & p7) U (!p4 | ((p4 & !p7) U (!p4 | ((p4 & p7) U (!p4 | (!p7 W !p4) | Gp7))))))))) : !G(!p3 -> Gp5) xor !G(!p7 -> G(p9 -> (!p5 & !p8 & X(!p5 U p2)))) : G(p6 -> ((!(!p1 & p7 & X(!p1 U (!p1 & !p3))) U (p1 | !p2)) | G!(p7 & XF!p3))) & (G((!p4 & XF!p5) -> XF(!p5 & F!p0)) <-> G((p5 & !p6) -> (p5 W (p5 & p7)))) : !G((p0 & p9) -> (!p7 W (!p0 | p4))) & !G((p1 & !p2) -> (!p8 W p2)) : ((Fp2 -> ((!p1 -> (!p2 U (p0 & !p2))) U p2)) -> G(p1 -> G(p9 -> (!p4 & p8 & X(!p4 U !p7))))) xor G(p1 -> Gp9) : !G((p5 & !p9 & F!p5) -> ((!p8 -> (p5 U (!p0 & p5))) U !p5)) -> !G((p6 & p9) -> (!p7 W !p9)) : G((!p1 & !p2) -> (p9 W p1)) <-> (G(p5 -> G(p0 -> F!p4)) -> (Fp6 -> ((!p5 & !p6) U (p6 | ((p5 & !p6) U (p6 | ((!p5 & !p6) U (p6 | ((p5 & !p6) U (p6 | (!p5 U p6))))))))))) : ((Fp1 -> ((p6 -> (!p1 U (!p1 & !p2 & X(!p1 U !p9)))) U p1)) <-> (F!p0 -> (p0 U (p0 & !p7 & X(p0 U !p9))))) | (Fp2 -> (p6 U (p2 | (p6 & !p7 & X(p6 U p1))))) Now we might want to clean this list a bit by relabeling each formula so is uses atomic propositions $\{p_0,p_1,...\}$ starting at 0 and without gap. #+BEGIN_SRC sh :exports both genltl --dac-pattern | ltlmix -n8 -P10 | ltlfilt --relabel=pnn #+END_SRC #+RESULTS: : !G((p0 & F!p1) -> (!p2 U (!p1 | (!p2 & !p3 & X(!p2 U p4))))) xor !G(!p5 -> ((!p1 & p2) U (!p2 | ((p1 & p2) U (!p2 | ((!p1 & p2) U (!p2 | ((p1 & p2) U (!p2 | (!p1 W !p2) | Gp1))))))))) : !G(!p0 -> Gp1) xor !G(!p2 -> G(p3 -> (!p1 & !p4 & X(!p1 U p5)))) : G(p0 -> ((!(!p1 & p2 & X(!p1 U (!p1 & !p3))) U (p1 | !p4)) | G!(p2 & XF!p3))) & (G((!p5 & XF!p6) -> XF(!p6 & F!p7)) <-> G((!p0 & p6) -> (p6 W (p2 & p6)))) : !G((p0 & p1) -> (!p2 W (!p0 | p3))) & !G((p4 & !p5) -> (!p6 W p5)) : ((Fp0 -> ((!p1 -> (!p0 U (!p0 & p2))) U p0)) -> G(p1 -> G(p3 -> (!p4 & p5 & X(!p4 U !p6))))) xor G(p1 -> Gp3) : !G((p0 & !p1 & F!p0) -> ((!p2 -> (p0 U (p0 & !p3))) U !p0)) -> !G((p1 & p4) -> (!p5 W !p1)) : G((!p0 & !p1) -> (p2 W p0)) <-> (G(p3 -> G(p4 -> F!p5)) -> (Fp6 -> ((!p3 & !p6) U (p6 | ((p3 & !p6) U (p6 | ((!p3 & !p6) U (p6 | ((p3 & !p6) U (p6 | (!p3 U p6))))))))))) : ((Fp0 -> ((p1 -> (!p0 U (!p0 & !p2 & X(!p0 U !p3)))) U p0)) <-> (F!p4 -> (p4 U (p4 & !p5 & X(p4 U !p3))))) | (Fp2 -> (p1 U (p2 | (p1 & !p5 & X(p1 U p0))))) ** Random conjunctions Some benchmarks (e.g., [[https://www.cs.rice.edu/~vardi/papers/time13.pdf][for LTL satisfiability]]) are built by conjunction of $L$ random formulas picked from a set of basic formulas. Each picked formula has its atomic proposition mapped to random literals built from a subset of $m$ atomic variables. Given a value for $m$, option =-P m= will achieve the second part of the above description. To build a conjunction of $L$ formulas, we need to ask for a tree of size $2L-1$ in which only the =and= operator is allowed. Here is an example with $L=10$ (hence =--tree-size=19=) and $m=50$. The example use a small set of three basic formulas $\{\mathsf{G}a,\mathsf{F}a,\mathsf{X}a\}$ for illustration, but in practice you should replace these =-f= options by =-F FILENAME= pointing to a file containing all the input formulas to select from. #+BEGIN_SRC sh :exports both ltlmix -fGa -fFa -fXa -n10 -P50 \ --tree-size=19 --boolean-prio=not=0,or=0,xor=0,equiv=0,implies=0 #+END_SRC #+RESULTS: #+begin_example Xp27 & F!p21 & G!p19 & X!p26 & Fp4 & Gp43 & Fp39 & Fp7 G!p28 & G!p30 & X!p34 & Fp21 & Gp3 & Fp6 & F!p18 & Xp5 & Fp8 & F!p12 X!p32 & Xp9 & Gp4 & Xp48 & F!p48 & X!p6 & Fp5 & Xp20 & Fp28 & Fp26 G!p46 & X!p14 & Fp29 & X!p36 & Fp12 & Xp47 & Fp42 & Gp14 & Fp19 G!p28 & Fp21 & Fp36 & F!p0 & G!p14 & Xp21 & F!p28 & G!p21 & Gp21 & Gp40 Gp3 & F!p48 & F!p28 & Xp7 & Gp8 & Xp42 & Gp0 & Xp36 & F!p2 & G!p0 Xp36 & Xp46 & F!p31 & Xp11 & Xp26 & G!p9 & F!p36 & X!p12 & Fp15 & Xp18 Xp9 & X!p33 & Fp44 & X!p13 & Gp37 & Xp19 & G!p43 & F!p34 & Gp36 & Gp10 Xp27 & Xp5 & Fp28 & Xp18 & G!p13 & Gp35 & Gp38 & G!p45 & G!p48 & Gp12 Xp7 & G!p48 & Xp14 & Fp24 & Xp43 & Fp47 & Fp14 & Gp30 & Xp23 & G!p31 #+end_example In fact building random conjunctions is common enough to have its own flag. Using =-C N= will see the tree size to $2N-1$ and disable all operators but =and=. The above command can therefore be reduced to #+BEGIN_SRC sh :exports both ltlmix -fGa -fFa -fXa -n10 -P50 -C10 #+END_SRC #+RESULTS: #+begin_example Xp27 & F!p21 & G!p19 & X!p26 & Fp4 & Gp43 & Fp39 & Fp7 G!p28 & G!p30 & X!p34 & Fp21 & Gp3 & Fp6 & F!p18 & Xp5 & Fp8 & F!p12 X!p32 & Xp9 & Gp4 & Xp48 & F!p48 & X!p6 & Fp5 & Xp20 & Fp28 & Fp26 G!p46 & X!p14 & Fp29 & X!p36 & Fp12 & Xp47 & Fp42 & Gp14 & Fp19 G!p28 & Fp21 & Fp36 & F!p0 & G!p14 & Xp21 & F!p28 & G!p21 & Gp21 & Gp40 Gp3 & F!p48 & F!p28 & Xp7 & Gp8 & Xp42 & Gp0 & Xp36 & F!p2 & G!p0 Xp36 & Xp46 & F!p31 & Xp11 & Xp26 & G!p9 & F!p36 & X!p12 & Fp15 & Xp18 Xp9 & X!p33 & Fp44 & X!p13 & Gp37 & Xp19 & G!p43 & F!p34 & Gp36 & Gp10 Xp27 & Xp5 & Fp28 & Xp18 & G!p13 & Gp35 & Gp38 & G!p45 & G!p48 & Gp12 Xp7 & G!p48 & Xp14 & Fp24 & Xp43 & Fp47 & Fp14 & Gp30 & Xp23 & G!p31 #+end_example Selecting 10 random conjuncts out of 3×50×2=300 possibilities has a 13.7% chance that at least 2 conjuncts will be identical (see [[https://en.wikipedia.org/wiki/Birthday_problem][Birthday paradox]]), so because of Spot's trivial rewritings, some of the above formulas may have fewer than 10 conjuncts.