org: cleanup tut40
* doc/org/tut40.org: Add more explanations and some cleanup. * python/spot/__init__.py (set_state_players, get_state_winners, get_state_players, set_state_player, get_state_winner, get_state_player, get_strategy): Add these methods to the twa_graph class for convenience. * NEWS, doc/org/tut.org: Mention tut40.org.
This commit is contained in:
parent
211e7d90d3
commit
88d0d2e112
4 changed files with 283 additions and 330 deletions
7
NEWS
7
NEWS
|
|
@ -284,8 +284,11 @@ New in spot 2.9.8.dev (not yet released)
|
||||||
|
|
||||||
Python:
|
Python:
|
||||||
|
|
||||||
- Bindings for functions related to games and aig-circuits.
|
- Bindings for functions related to games.
|
||||||
See https://spot.lrde.epita.fr/ipynb/games.html for examples.
|
See https://spot.lrde.epita.fr/ipynb/games.html and
|
||||||
|
https://spot.lrde.epita.fr/tut40.html for examples.
|
||||||
|
|
||||||
|
- Bindings for functions related to syntethis and aig-circuits.
|
||||||
|
|
||||||
- spot::twa::prop_set was previously abstent from the Python
|
- spot::twa::prop_set was previously abstent from the Python
|
||||||
bindings, making it impossible to call make_twa_graph() for copying
|
bindings, making it impossible to call make_twa_graph() for copying
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ three interfaces supported by Spot: shell commands, Python, or C++.
|
||||||
- [[file:tut22.org][Creating an automaton by adding states and transitions]]
|
- [[file:tut22.org][Creating an automaton by adding states and transitions]]
|
||||||
- [[file:tut23.org][Creating an alternating automaton by adding states and transitions]]
|
- [[file:tut23.org][Creating an alternating automaton by adding states and transitions]]
|
||||||
- [[file:tut24.org][Iterating over alternating automata]]
|
- [[file:tut24.org][Iterating over alternating automata]]
|
||||||
|
- [[file:tut40.org][Creating a solving a safety game to decide direct simulation]]
|
||||||
- [[file:tut52.org][Creating an explicit Kripke structure]]
|
- [[file:tut52.org][Creating an explicit Kripke structure]]
|
||||||
- [[file:tut90.org][Using the =bdd_dict= to associate atomic proposition to BDD
|
- [[file:tut90.org][Using the =bdd_dict= to associate atomic proposition to BDD
|
||||||
variables, or allocate anonymous BDD variables (advanced)]]
|
variables, or allocate anonymous BDD variables (advanced)]]
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#+TITLE: Using games to compute simulation
|
#+TITLE: Using games to check a simulation
|
||||||
#+DESCRIPTION: Code example for iterating over ω-automata in Spot
|
#+DESCRIPTION: Code example for using games in Spot
|
||||||
#+INCLUDE: setup.org
|
#+INCLUDE: setup.org
|
||||||
#+HTML_LINK_UP: tut.html
|
#+HTML_LINK_UP: tut.html
|
||||||
#+PROPERTY: header-args:sh :results verbatim :exports both
|
#+PROPERTY: header-args:sh :results verbatim :exports both
|
||||||
#+PROPERTY: header-args:python :results output :exports both
|
#+PROPERTY: header-args:python :results output :exports both
|
||||||
#+PROPERTY: header-args:C+++ :results verbatim :exports both
|
#+PROPERTY: header-args:C+++ :results verbatim :exports both
|
||||||
|
|
||||||
This example demonstrates how to find the simulations between states using
|
This example demonstrates how to use Spot's game interface to compute
|
||||||
games in C++ and python. This algorithm is describe in Fair Simulation
|
a simulation-relation between the states of an automaton. This
|
||||||
Relations, Parity Games, and State Space Reduction for Büchi Automata by R.
|
algorithm is inspired from [[https://homepages.inf.ed.ac.uk/kousha/siam_j2005.pdf][Fair Simulation Relations, Parity Games,
|
||||||
Schuller Borbely, Kousha Etessami and Thomas Wilke.
|
and State Space Reduction for Büchi Automata (Kousha Etessami and
|
||||||
|
Thomas Wilke, and Rebecca A. Schuller)]].
|
||||||
|
|
||||||
The simulation based reduction (spot.simuation() and spot.reduce_direct_sim())
|
The code below is intended for demonstration of how to construct and
|
||||||
does not use this method as is it slower.
|
use games. Spot contains some other (and faster) implementation to
|
||||||
|
reduce an automaton using simulation-based reductions (see
|
||||||
|
=spot.simulation()= and =spot.reduce_direct_sim()=).
|
||||||
|
|
||||||
When all the moves are in the game, we check if the spoiler has a winning
|
Let us start with a definition of simulation for transition-based
|
||||||
strategy from a pair of state. If yes, there is simulation otherwise there are
|
generalized-Büchi automata: A state $s'$ simulates $s$ iff for any
|
||||||
no.
|
transition $(s,c,a,d)$ leaving $s$, there exists a transition
|
||||||
|
$(s',c',a',d')$ leaving $s'$ with a condition $c'$ that covers $c$,
|
||||||
|
some colors $a'\supseteq a$ that covers the colors of $a$ other
|
||||||
|
transition, and reaching a destination state $d'$ that simulates $d$.
|
||||||
|
|
||||||
We will compute the simulation between 4 and 0 in the following automaton.
|
In the following automaton, for instance, XXX
|
||||||
|
|
||||||
#+NAME: tut40in
|
#+NAME: tut40in
|
||||||
#+BEGIN_SRC hoa
|
#+BEGIN_SRC hoa
|
||||||
|
|
@ -27,23 +33,21 @@ HOA: v1
|
||||||
States: 6
|
States: 6
|
||||||
Start: 0
|
Start: 0
|
||||||
AP: 2 "a" "b"
|
AP: 2 "a" "b"
|
||||||
acc-name: co-Buchi
|
Acceptance: 1 Inf(0)
|
||||||
Acceptance: 1 Fin(0)
|
|
||||||
properties: trans-labels explicit-labels state-acc very-weak
|
|
||||||
--BODY--
|
--BODY--
|
||||||
State: 0
|
State: 0
|
||||||
[1] 1
|
[1] 1
|
||||||
[1] 2
|
[1] 2
|
||||||
State: 1 {0}
|
State: 1
|
||||||
[0&1] 1
|
[0&1] 1
|
||||||
State: 2
|
State: 2
|
||||||
[0] 3
|
[0] 3
|
||||||
State: 3
|
State: 3
|
||||||
[1] 3
|
[1] 3 {0}
|
||||||
State: 4
|
State: 4
|
||||||
[1] 5
|
[1] 5
|
||||||
State: 5
|
State: 5
|
||||||
[0] 5
|
[0] 5 {0}
|
||||||
--END--
|
--END--
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
@ -52,39 +56,9 @@ State: 5
|
||||||
cat >tut40.hoa <<EOF
|
cat >tut40.hoa <<EOF
|
||||||
<<tut40in>>
|
<<tut40in>>
|
||||||
EOF
|
EOF
|
||||||
autfilt --dot tut40.hoa
|
autfilt --dot='.#' tut40.hoa
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
#+RESULTS: tut40dot
|
|
||||||
#+begin_example
|
|
||||||
digraph "" {
|
|
||||||
rankdir=LR
|
|
||||||
label=<<br/>[co-Büchi]>
|
|
||||||
labelloc="t"
|
|
||||||
node [shape="circle"]
|
|
||||||
node [style="filled", fillcolor="#ffffa0"]
|
|
||||||
fontname="Lato"
|
|
||||||
node [fontname="Lato"]
|
|
||||||
edge [fontname="Lato"]
|
|
||||||
node[fontsize=12] fontsize=12 stylesheet="spot.css" edge[arrowhead=vee, arrowsize=.7, fontsize=12]
|
|
||||||
I [label="", style=invis, width=0]
|
|
||||||
I -> 0
|
|
||||||
0 [label=<0>]
|
|
||||||
0 -> 1 [label=<b>]
|
|
||||||
0 -> 2 [label=<b>]
|
|
||||||
1 [label=<1>, peripheries=2]
|
|
||||||
1 -> 1 [label=<a & b>]
|
|
||||||
2 [label=<2>]
|
|
||||||
2 -> 3 [label=<a>]
|
|
||||||
3 [label=<3>]
|
|
||||||
3 -> 3 [label=<b>]
|
|
||||||
4 [label=<4>]
|
|
||||||
4 -> 5 [label=<b>]
|
|
||||||
5 [label=<5>]
|
|
||||||
5 -> 5 [label=<a>]
|
|
||||||
}
|
|
||||||
#+end_example
|
|
||||||
|
|
||||||
#+BEGIN_SRC dot :file tut40in.svg :var txt=tut40dot :exports results
|
#+BEGIN_SRC dot :file tut40in.svg :var txt=tut40dot :exports results
|
||||||
$txt
|
$txt
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
@ -92,122 +66,175 @@ digraph "" {
|
||||||
#+RESULTS:
|
#+RESULTS:
|
||||||
[[file:tut40in.svg]]
|
[[file:tut40in.svg]]
|
||||||
|
|
||||||
|
|
||||||
|
Whether two states are in simulation can be decided as a game between
|
||||||
|
two players. If the game is in state $(q,q')$, spoiler (player 0)
|
||||||
|
first selects a transition from state $q$, and duplicator (player 1)
|
||||||
|
then has to chose a compatible transition from state $q'$. Duplicator
|
||||||
|
of course wins if it always manages to select compatibles transitions,
|
||||||
|
otherwise spoiler wins.
|
||||||
|
|
||||||
|
The game arena can be encoded by associating each state to a pair of
|
||||||
|
integers. States owned by player 0 (rounded rectangles) are pairs
|
||||||
|
$(q,q')$ denoting the position of each player. States owned by player
|
||||||
|
1 (diamonds) are pairs $(e,q')$ where $e$ is the number of the
|
||||||
|
edges that player 0 just took (those numbers appears as =#1=, =#2=,
|
||||||
|
etc. in the previous picture).
|
||||||
|
|
||||||
|
Here is how the game arena look like starting from $(q,q')=(4,0)$:
|
||||||
|
|
||||||
|
#+NAME: game40
|
||||||
|
#+BEGIN_SRC python :exports results :noweb yes
|
||||||
|
<<build_game>>
|
||||||
|
aut = spot.automaton("""
|
||||||
|
<<tut40in>>
|
||||||
|
""")
|
||||||
|
g = direct_sim_game(aut, 4, 0)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+NAME: game40unsolved
|
||||||
|
#+BEGIN_SRC python :exports none :noweb yes
|
||||||
|
<<game40>>
|
||||||
|
print(g.to_str('dot', '.g'))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC dot :file tut40gameunsolved.svg :var txt=game40unsolved :exports results
|
||||||
|
$txt
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:tut40gameunsolved.svg]]
|
||||||
|
|
||||||
|
In this game, player 1, wins if it has a strategy to force the game to
|
||||||
|
satisfy the acceptance condition. Here the acceptance condition is
|
||||||
|
just true, so any infinite play will satisfy it.
|
||||||
|
|
||||||
|
Clearly, it is enough for player 1 to always go to $(5,1)$ when
|
||||||
|
possible. If Spot is used to solve this game, the result can be
|
||||||
|
presented as follows, where greens states represents states from which
|
||||||
|
player 1 has a winning strategy, and red states are states from which
|
||||||
|
player 0 has a winning strategy. The highlighted arrows show those
|
||||||
|
strategies.
|
||||||
|
|
||||||
|
#+NAME: game40solved
|
||||||
|
#+BEGIN_SRC python :exports none :noweb yes
|
||||||
|
<<game40>>
|
||||||
|
spot.solve_safety_game(g)
|
||||||
|
spot.highlight_strategy(g)
|
||||||
|
print(g.to_str('dot', '.g'))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC dot :file tut40gamesolved.svg :var txt=game40solved :exports results
|
||||||
|
$txt
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:tut40gamesolved.svg]]
|
||||||
|
|
||||||
|
|
||||||
|
Since player 1 is winning from state $(4,0)$, we know that state 4
|
||||||
|
simulates state 0. Also since player 1 would also win from state
|
||||||
|
$(5,1)$, we can tell that state 5 simulates state 1. We also learn
|
||||||
|
that state 5 does not simulates states 2 and 3. We could build other
|
||||||
|
games, or add more state to this game, to learn about other pairs of
|
||||||
|
states.
|
||||||
|
|
||||||
* Python
|
* Python
|
||||||
|
|
||||||
|
We now look at how to create such a game in Python.
|
||||||
|
|
||||||
|
Essentially, a game in Spot is just an automaton equiped with a
|
||||||
|
special property to indicate the owner of each states. So it can be
|
||||||
|
created using the usual interface:
|
||||||
|
|
||||||
First a need to create a game. The game is build by alternating the possible
|
|
||||||
moves of the two players: the spoiler (even) and the duplicator (odd). The
|
|
||||||
spoiler starts from its state and chose a transition and move to the
|
|
||||||
destination. Then, the duplicator search for a transition from its state with a
|
|
||||||
compatible condition and acceptance.
|
|
||||||
|
|
||||||
#+NAME: build_game
|
#+NAME: build_game
|
||||||
#+BEGIN_SRC python :exports code
|
#+BEGIN_SRC python :exports code
|
||||||
import spot
|
import spot
|
||||||
from spot import buddy
|
from spot import buddy
|
||||||
def direct_sim_game(aut, s1, s2):
|
|
||||||
if s1 >= aut.num_states() or s2 >= aut.num_states():
|
|
||||||
raise ValueError('invalid state number')
|
|
||||||
|
|
||||||
game = spot.make_twa_graph(aut.get_dict())
|
def direct_sim_game(aut, s1, s2):
|
||||||
|
if s1 >= aut.num_states() or s2 >= aut.num_states():
|
||||||
|
raise ValueError('invalid state number')
|
||||||
|
assert aut.acc().is_generalized_buchi()
|
||||||
|
|
||||||
# The names are a pair of int. If even owns the state, the first integer
|
game = spot.make_twa_graph()
|
||||||
# is the number of the edge that spoiler has taken during this turn (so
|
# The names of the states are pairs of integers
|
||||||
# we have the current position of the spoiler (the destination of the
|
# (q,q') for states owned by player 0
|
||||||
# edge), the condition and acceptance that duplicator must find). If
|
# (e,q') for states owned by player 1
|
||||||
# it is an odd state, the first integer is the state where the spoiler
|
# These arrays are indiced by state numbers.
|
||||||
# is. In either case, the second is always the duplicator's state.
|
names = []
|
||||||
names = []
|
owners = []
|
||||||
owners = []
|
# The reverse assotiation (x,y) -> state number
|
||||||
|
# must be kept for each player, since (x,y) can mean two different thing.
|
||||||
|
s_orig_states = {}
|
||||||
|
d_orig_states = {}
|
||||||
|
# a list of player 0 states to process
|
||||||
|
todo = []
|
||||||
|
|
||||||
# Keep the game's state from its name. We need two maps as two states can
|
# Create the state (i, j) for a player if it does not exist yet and
|
||||||
# have the same name (but not the same owner). For example (0, 1) can
|
# returns the state's number in the game.
|
||||||
# describe a spoiler state (the edge 0, the state 1) and a duplicator
|
def get_game_state(player, i, j):
|
||||||
# state (the states 0 and 1).
|
orig_state = s_orig_states if player else d_orig_states
|
||||||
s_orig_states = {}
|
if (i, j) in orig_state:
|
||||||
d_orig_states = {}
|
|
||||||
|
|
||||||
todo = []
|
|
||||||
|
|
||||||
# Create the state (i, j) for a player if it does not exist yet and
|
|
||||||
# returns its state's number in the game.
|
|
||||||
def get_and_create_game_state(player, i, j):
|
|
||||||
orig_state = s_orig_states if player else d_orig_states
|
|
||||||
|
|
||||||
if not (i, j) in orig_state:
|
|
||||||
s = game.new_state()
|
|
||||||
|
|
||||||
names.append((i, j))
|
|
||||||
owners.append(player)
|
|
||||||
orig_state[(i, j)] = s
|
|
||||||
|
|
||||||
# If it is the duplicator turn and the state does not exist
|
|
||||||
# yet, we need to process it.
|
|
||||||
if not player:
|
|
||||||
todo.append(s)
|
|
||||||
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
return orig_state[(i, j)]
|
return orig_state[(i, j)]
|
||||||
|
s = game.new_state()
|
||||||
|
names.append((i, j))
|
||||||
|
owners.append(player)
|
||||||
|
orig_state[(i, j)] = s
|
||||||
|
# If it is a new state for Player 0 (spoiler)
|
||||||
|
# we need to process it.
|
||||||
|
if not player:
|
||||||
|
todo.append(s)
|
||||||
|
return s
|
||||||
|
|
||||||
get_and_create_game_state(False, s1, s2);
|
game.set_init_state(get_game_state(False, s1, s2))
|
||||||
(all_inf, _) = aut.get_acceptance().used_inf_fin_sets()
|
while todo:
|
||||||
|
cur = todo.pop()
|
||||||
|
# todo contains only player 0's states, named with pairs
|
||||||
|
# of states.
|
||||||
|
(s_src, d_src) = names[cur]
|
||||||
|
|
||||||
while todo:
|
# Player 0 is allowed to pick edge from s_src:
|
||||||
cur = todo.pop()
|
for s_edge in aut.out(s_src):
|
||||||
(s_src, d_src) = names[cur]
|
edge_idx = aut.edge_number(s_edge)
|
||||||
|
st2 = get_game_state(True, edge_idx, d_src)
|
||||||
|
# label the edge with true, because it's an automaton,
|
||||||
|
# but we do not use this label for the game.
|
||||||
|
game.new_edge(cur, st2, buddy.bddtrue)
|
||||||
|
|
||||||
# Spoiler starts by picking an edge from s_src. If it does not exist
|
# Player 1 then try to find an edge with the
|
||||||
# yet, we add a new edge in the game from (s_src,d_src) to
|
# a compatible same condition and colors, from d_src.
|
||||||
# (edge_idx,d_src).
|
for d_edge in aut.out(d_src):
|
||||||
for s_edge in aut.out(s_src):
|
if (buddy.bdd_implies(d_edge.cond, s_edge.cond) \
|
||||||
edge_idx = aut.edge_number(s_edge)
|
and d_edge.acc.subset(s_edge.acc)):
|
||||||
cur2 = get_and_create_game_state(True, edge_idx, d_src)
|
st3 = get_game_state(False, s_edge.dst, d_edge.dst)
|
||||||
|
game.new_edge(st2, st3, buddy.bddtrue)
|
||||||
|
|
||||||
game.new_edge(cur, cur2, buddy.bddtrue)
|
# Name each state with a string, just so we can read the pairs
|
||||||
|
# when the automaton is displayed.
|
||||||
# Duplicator try to find an edge with the same condition from d_src.
|
game.set_state_names(list(map(str, names)))
|
||||||
# If it does not exist yet, it adds a new edge from
|
# This only line is actually what turns an automaton into a game!
|
||||||
# (s_edge.dst,d_src) to (s_edge.dst, d_edge.dst).
|
game.set_state_players(owners)
|
||||||
for d_edge in aut.out(d_src):
|
return game
|
||||||
if buddy.bdd_implies(d_edge.cond, s_edge.cond) \
|
|
||||||
and (s_edge.acc ^ all_inf).subset(d_edge.acc ^ all_inf):
|
|
||||||
|
|
||||||
cur3 = get_and_create_game_state(False, s_edge.dst, d_edge.dst)
|
|
||||||
game.new_edge(cur2, cur3, buddy.bddtrue)
|
|
||||||
|
|
||||||
spot.set_state_players(game, owners)
|
|
||||||
names = [s for s in map(str, names)]
|
|
||||||
game.set_state_names(names)
|
|
||||||
|
|
||||||
return game
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Then we solve it. This game is a safety game, the odd player tries to block
|
To solve a safety game =g= that has been created by the above method,
|
||||||
the even player into a dead end. Even win if it has a winning strategy, aka
|
it is enough to just call =solve_safety_game(g)=. This function
|
||||||
there is no dead end in the game.
|
returns the player winning in the initial state. However, as a
|
||||||
|
side-effect it defines additional automaton properties that indicate
|
||||||
|
the winner of each state, and the associated strategy.
|
||||||
|
|
||||||
#+BEGIN_SRC :export code
|
Therefore to list all simulation pairs we learned from a game starting
|
||||||
spot.solve_safety_game(g)
|
in state $(i,j)$, we could proceed as follow:
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Next, we get the winners for each state. We only check the even's state as it
|
|
||||||
is the only ones that describe states pairs (the odd's ones describe the
|
|
||||||
edge taken and a state). If odd wins from this state, then the first state
|
|
||||||
simulates the second.
|
|
||||||
|
|
||||||
#+NAME: computesim_tut40
|
#+NAME: computesim_tut40
|
||||||
#+BEGIN_SRC python :exports code
|
#+BEGIN_SRC python :exports code
|
||||||
def compute_simulation(aut, i, j):
|
def list_simulations(aut, i, j):
|
||||||
g = direct_sim_game(aut, i, j)
|
g = direct_sim_game(aut, i, j)
|
||||||
spot.solve_safety_game(g)
|
spot.solve_safety_game(g)
|
||||||
|
|
||||||
winners = spot.get_state_winners(g)
|
winners = g.get_state_winners()
|
||||||
owners = spot.get_state_players(g)
|
owners = g.get_state_players()
|
||||||
names = g.get_state_names()
|
names = g.get_state_names()
|
||||||
|
|
||||||
simulations = []
|
simulations = []
|
||||||
|
|
@ -218,220 +245,139 @@ def compute_simulation(aut, i, j):
|
||||||
return simulations
|
return simulations
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
#+RESULTS: computesim_tut40
|
On our running example, that gives:
|
||||||
|
|
||||||
Compute all simulations found when checking if 4 simulates 0.
|
#+BEGIN_SRC python :results verbatim :exports both :noweb strip-export
|
||||||
|
<<game40>>
|
||||||
#+BEGIN_SRC python :exports code
|
<<computesim_tut40>>
|
||||||
print(compute_simulation(aut, 4, 0))
|
print(list_simulations(aut, 4, 0))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
#+RESULTS:
|
#+RESULTS:
|
||||||
|
|
||||||
#+BEGIN_SRC python :exports results :results verbatim :noweb yes
|
|
||||||
import spot
|
|
||||||
from spot import buddy
|
|
||||||
|
|
||||||
aut = spot.automaton('''
|
|
||||||
<<tut40in>>
|
|
||||||
''')
|
|
||||||
|
|
||||||
<<build_game>>
|
|
||||||
|
|
||||||
<<computesim_tut40>>
|
|
||||||
|
|
||||||
print(compute_simulation(aut, 4, 0))
|
|
||||||
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
: [(4, 0), (5, 1)]
|
|
||||||
|
|
||||||
* C++
|
* C++
|
||||||
|
|
||||||
|
Here is some almost equivalent code in C++.
|
||||||
|
|
||||||
The equivalent code in C++:
|
Here instead of naming states with strings, we use the "product-states"
|
||||||
|
property, which is usually used to display pair of integers that come from a
|
||||||
|
product of automata.
|
||||||
|
|
||||||
#+NAME: cppCompute
|
#+NAME: cppCompute
|
||||||
#+BEGIN_SRC C++ :exports code
|
#+BEGIN_SRC C++ :exports code
|
||||||
#include <spot/twaalgos/game.hh>
|
#include <spot/twaalgos/game.hh>
|
||||||
#include <spot/twa/twagraph.hh>
|
#include <spot/twa/twagraph.hh>
|
||||||
|
|
||||||
spot::twa_graph_ptr direct_sim_game(spot::const_twa_graph_ptr aut,
|
spot::twa_graph_ptr direct_sim_game(spot::const_twa_graph_ptr aut,
|
||||||
unsigned s1, unsigned s2)
|
unsigned s1, unsigned s2)
|
||||||
{
|
|
||||||
if (s1 >= aut->num_states() || s2 >= aut->num_states())
|
|
||||||
throw std::runtime_error("direct_sim_game(): invalid state number");
|
|
||||||
|
|
||||||
auto game = spot::make_twa_graph(aut->get_dict());
|
|
||||||
game->copy_ap_of(aut);
|
|
||||||
|
|
||||||
auto names = new std::vector<std::pair<unsigned, unsigned>>();
|
|
||||||
game->set_named_prop("product-states", names);
|
|
||||||
names->reserve(aut->num_states());
|
|
||||||
|
|
||||||
auto owners = new std::vector<bool>();
|
|
||||||
game->set_named_prop("state-player", owners);
|
|
||||||
owners->reserve(aut->num_states());
|
|
||||||
|
|
||||||
std::map<std::pair<unsigned, unsigned>, unsigned> s_orig_states;
|
|
||||||
std::map<std::pair<unsigned, unsigned>, unsigned> d_orig_states;
|
|
||||||
std::vector<unsigned> todo;
|
|
||||||
todo.reserve(aut->num_states());
|
|
||||||
auto& g = game->get_graph();
|
|
||||||
|
|
||||||
auto all_inf = aut->get_acceptance().used_inf_fin_sets().first;
|
|
||||||
|
|
||||||
auto new_state = [&](bool player, unsigned s1, unsigned s2)
|
|
||||||
{
|
{
|
||||||
unsigned s;
|
if (s1 >= aut->num_states() || s2 >= aut->num_states())
|
||||||
auto& m = player ? s_orig_states : d_orig_states;
|
throw std::runtime_error("direct_sim_game(): invalid state number");
|
||||||
|
|
||||||
auto it = std::find_if(m.begin(), m.end(), [s1, s2](auto elm)
|
auto game = spot::make_twa_graph(spot::make_bdd_dict());
|
||||||
{ return s1 == elm.first.first && s2 == elm.first.second; });
|
|
||||||
|
|
||||||
if (it == m.end())
|
auto names = new std::vector<std::pair<unsigned, unsigned>>();
|
||||||
{
|
game->set_named_prop("product-states", names);
|
||||||
s = game->new_state();
|
|
||||||
names->emplace_back(s1, s2);
|
|
||||||
owners->push_back(player);
|
|
||||||
m.insert({{s1, s2}, s});
|
|
||||||
|
|
||||||
if (!player)
|
auto owners = new std::vector<bool>();
|
||||||
todo.push_back(s);
|
game->set_named_prop("state-player", owners);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s = it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
std::map<std::pair<unsigned, unsigned>, unsigned> s_orig_states;
|
||||||
};
|
std::map<std::pair<unsigned, unsigned>, unsigned> d_orig_states;
|
||||||
|
std::vector<unsigned> todo;
|
||||||
|
|
||||||
new_state(false, s1, s2);
|
auto new_state = [&](bool player, unsigned s1, unsigned s2)
|
||||||
|
|
||||||
while (!todo.empty())
|
|
||||||
{
|
{
|
||||||
unsigned cur = todo.back();
|
auto& m = player ? s_orig_states : d_orig_states;
|
||||||
unsigned s_src = (*names)[cur].first;
|
if (auto it = m.find({s1, s2}); it != m.end())
|
||||||
unsigned d_src = (*names)[cur].second;
|
return it->second;
|
||||||
todo.pop_back();
|
unsigned s = game->new_state();
|
||||||
|
names->emplace_back(s1, s2);
|
||||||
|
owners->push_back(player);
|
||||||
|
m.insert({{s1, s2}, s});
|
||||||
|
if (!player)
|
||||||
|
todo.push_back(s);
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
for (const auto& s_edge : aut->out(s_src))
|
game->set_init_state(new_state(false, s1, s2));
|
||||||
{
|
while (!todo.empty())
|
||||||
unsigned edge_idx = g.index_of_edge(s_edge);
|
|
||||||
unsigned cur2 = new_state(true, edge_idx, d_src);
|
|
||||||
|
|
||||||
game->new_edge(cur, cur2, bddtrue);
|
|
||||||
|
|
||||||
for (const auto& d_edge : aut->out(d_src))
|
|
||||||
{
|
|
||||||
if (bdd_implies(d_edge.cond, s_edge.cond)
|
|
||||||
&& (s_edge.acc ^ all_inf).subset(d_edge.acc ^ all_inf))
|
|
||||||
{
|
|
||||||
unsigned cur3 = new_state(false, s_edge.dst, d_edge.dst);
|
|
||||||
game->new_edge(cur2, cur3, bddtrue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return game;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool compute_simulation(spot::const_twa_graph_ptr aut,
|
|
||||||
unsigned i, unsigned j,
|
|
||||||
std::vector<bool>& can_sim)
|
|
||||||
{
|
|
||||||
auto g = direct_sim_game(aut, i, j);
|
|
||||||
bool i_simulates_j = spot::solve_safety_game(g);
|
|
||||||
|
|
||||||
std::vector<bool> winners = spot::get_state_winners(g);
|
|
||||||
std::vector<bool> owners = spot::get_state_players(g);
|
|
||||||
|
|
||||||
typedef std::vector<std::pair<unsigned, unsigned>> names_t;
|
|
||||||
auto names = *g->get_named_prop<names_t>("product-states");
|
|
||||||
|
|
||||||
unsigned n = g->num_states();
|
|
||||||
unsigned n_aut = aut->num_states();
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < n; ++i)
|
|
||||||
if (winners[i] && !owners[i])
|
|
||||||
{
|
{
|
||||||
auto [u, v] = names[i];
|
unsigned cur = todo.back();
|
||||||
can_sim[u * n_aut + v] = true;
|
todo.pop_back();
|
||||||
|
auto [s_src, d_src] = (*names)[cur];
|
||||||
|
|
||||||
|
for (const auto& s_edge : aut->out(s_src))
|
||||||
|
{
|
||||||
|
unsigned edge_idx = aut->edge_number(s_edge);
|
||||||
|
unsigned st2 = new_state(true, edge_idx, d_src);
|
||||||
|
game->new_edge(cur, st2, bddtrue);
|
||||||
|
for (const auto& d_edge : aut->out(d_src))
|
||||||
|
if (bdd_implies(d_edge.cond, s_edge.cond)
|
||||||
|
&& d_edge.acc.subset(s_edge.acc))
|
||||||
|
{
|
||||||
|
unsigned st3 = new_state(false, s_edge.dst, d_edge.dst);
|
||||||
|
game->new_edge(st2, st3, bddtrue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return i_simulates_j;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<int,int>>
|
||||||
|
list_simulation(spot::const_twa_graph_ptr aut,
|
||||||
|
unsigned i, unsigned j)
|
||||||
|
{
|
||||||
|
auto g = direct_sim_game(aut, i, j);
|
||||||
|
spot::solve_safety_game(g);
|
||||||
|
|
||||||
|
const std::vector<bool>& winners = spot::get_state_winners(g);
|
||||||
|
const std::vector<bool>& owners = spot::get_state_players(g);
|
||||||
|
|
||||||
|
typedef std::vector<std::pair<unsigned, unsigned>> names_t;
|
||||||
|
auto names = *g->get_named_prop<names_t>("product-states");
|
||||||
|
|
||||||
|
std::vector<std::pair<int,int>> res;
|
||||||
|
|
||||||
|
unsigned n = g->num_states();
|
||||||
|
for (unsigned i = 0; i < n; ++i)
|
||||||
|
if (winners[i] && !owners[i])
|
||||||
|
res.emplace_back(names[i]);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
#+RESULTS: cppCompute
|
Now to execute the above code on our example automaton, we just
|
||||||
|
need to read the automaton from a file.
|
||||||
Computes and print the simulations found when checking if 4 simulates 0.
|
|
||||||
|
|
||||||
#+NAME: cppCallCompute
|
|
||||||
#+BEGIN_SRC text :exports none
|
|
||||||
unsigned n = aut->num_states();
|
|
||||||
std::vector<bool> can_sim(n*n);
|
|
||||||
|
|
||||||
compute_simulation(aut, 4, 0, can_sim);
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < n; ++i)
|
|
||||||
for (unsigned j = 0; j < n; ++j)
|
|
||||||
if (can_sim[i * n + j])
|
|
||||||
std::cout << i << " simulates " << j << '\n';
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+BEGIN_SRC C++ :exports code :noweb yes
|
|
||||||
<<cppCallCompute>>
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS: cppCallCompute
|
|
||||||
|
|
||||||
#+NAME: finalcpp
|
#+NAME: finalcpp
|
||||||
#+BEGIN_SRC C++ :results verbatim :exports results :noweb yes
|
#+BEGIN_SRC C++ :results verbatim :exports both :noweb strip-export
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <spot/twa/twagraph.hh>
|
#include <spot/twa/twagraph.hh>
|
||||||
|
#include <spot/parseaut/public.hh>
|
||||||
<<cppCompute>>
|
<<cppCompute>>
|
||||||
|
int main()
|
||||||
int main()
|
{
|
||||||
{
|
spot::parsed_aut_ptr pa = parse_aut("tut40.hoa", spot::make_bdd_dict());
|
||||||
spot::bdd_dict_ptr dict = spot::make_bdd_dict();
|
if (pa->format_errors(std::cerr))
|
||||||
spot::twa_graph_ptr aut = make_twa_graph(dict);
|
return 1;
|
||||||
|
if (pa->aborted)
|
||||||
bdd p1 = bdd_ithvar(aut->register_ap("p1"));
|
{
|
||||||
bdd p2 = bdd_ithvar(aut->register_ap("p2"));
|
std::cerr << "--ABORT-- read\n";
|
||||||
|
return 1;
|
||||||
aut->set_acceptance(1, "t");
|
}
|
||||||
aut->prop_state_acc(true);
|
for (auto [i,j]: list_simulation(pa->aut, 4, 0))
|
||||||
|
std::cout << i << " simulates " << j << '\n';
|
||||||
aut->new_states(6);
|
return 0;
|
||||||
aut->set_init_state(0U);
|
}
|
||||||
|
|
||||||
aut->new_edge(0, 1, p2);
|
|
||||||
aut->new_edge(1, 1, p1 & p2, {0});
|
|
||||||
aut->new_edge(0, 2, p2);
|
|
||||||
aut->new_edge(2, 3, p1);
|
|
||||||
aut->new_edge(3, 3, p2);
|
|
||||||
aut->new_edge(4, 5, p2);
|
|
||||||
aut->new_edge(5, 5, p1);
|
|
||||||
|
|
||||||
unsigned n = aut->num_states();
|
|
||||||
std::vector<bool> can_sim(n*n);
|
|
||||||
|
|
||||||
compute_simulation(aut, 4, 0, can_sim);
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < n; ++i)
|
|
||||||
for (unsigned j = 0; j < n; ++j)
|
|
||||||
if (can_sim[i * n + j])
|
|
||||||
std::cout << i << " simulates " << j << '\n';
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
#+RESULTS: finalcpp
|
#+RESULTS: finalcpp
|
||||||
: 4 simulates 0
|
: 4 simulates 0
|
||||||
: 5 simulates 1
|
: 5 simulates 1
|
||||||
|
|
||||||
|
|
||||||
|
#+BEGIN_SRC sh :results silent :exports results
|
||||||
|
rm -f tut40.hoa
|
||||||
|
#+END_SRC
|
||||||
|
|
|
||||||
|
|
@ -1031,7 +1031,10 @@ def _add_twa_graph(meth, name=None):
|
||||||
|
|
||||||
for meth in ('scc_filter', 'scc_filter_states',
|
for meth in ('scc_filter', 'scc_filter_states',
|
||||||
'is_deterministic', 'is_unambiguous',
|
'is_deterministic', 'is_unambiguous',
|
||||||
'contains'):
|
'contains', 'get_strategy',
|
||||||
|
'set_state_players', 'get_state_players',
|
||||||
|
'set_state_player', 'get_state_player',
|
||||||
|
'get_state_winners', 'get_state_winner'):
|
||||||
_add_twa_graph(meth)
|
_add_twa_graph(meth)
|
||||||
_add_twa_graph('are_equivalent', 'equivalent_to')
|
_add_twa_graph('are_equivalent', 'equivalent_to')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue