TSys class¶
Deterministic Transition System¶
Represents a transition system [Principles of Model Checking, Def. 2.1].
$$
TSys = (S, A, T, AP, L)
$$
In the TSys
class, each component is represented as a function.
- The set of states $S$ is represented by
TSys.states
function, - The set of actions $A$ is represented by
TSys.actions
function, - The transition function $T$ is represented by
TSys.delta
function, - The set of atomic propositions is represented by
TSys.atoms
function, - The labeling function $L$ is represented by
TSys.label
function.
All of the above functions are marked abstract. The recommended way to use TSys
class is by subclassing it and implementing its component functions.
The example shows an example of defining a deterministic transition system.
Car Parking Transition System¶
Consider a car parking with $n$ slots where cars can enter
or exit
.
There are two properties of interest, namely whether the parking lot is empty or full.
The CarParking
class demonstrates how a parameterized transition system can be defined.
# This code block is necessary only when using `ggsolver:v0.1` docker image.
import sys
sys.path.append('/home/ggsolver/')
from examples.jupyter_patch import *
import logging
logger = logging.getLogger()
logger.setLevel(logging.ERROR)
A deterministic transition system is defined as follows:
- First we define a class that inherits from
ggsolver.models.TSys
class. We will call itCarParking
. - Define its
__init__
method to define instance variables that store input parameters. In our case, the only input parameter is the number of slots in parking lot:num_slots
. - Implement the component functions
states, actions, delta, atoms, label
that define the transition system.
from ggsolver.models import TSys
class CarParking(TSys):
def __init__(self, num_slots):
super(CarParking, self).__init__()
self.num_slots = num_slots
def states(self):
"""
To determine whether parking is full or empty, we can maintain a count of the number of cars
in the parking lot. Hence, the states can be represented by integers from 0 to num_slots.
"""
return list(range(self.num_slots + 1))
def actions(self):
"""
There are two actions: enter and exit, that represent a car has entered or exited.
"""
return ["enter", "exit"]
def delta(self, state, act):
"""
The transition function should determine the next state, i.e. how many cars will be there in the parking lot, based on how many cars are currently in the parking lot and the action: whether a car is entering or exiting.
"""
if state == 0 and act == "exit":
return 0
elif state == self.num_slots and act == "enter":
return self.num_slots
elif act == "enter":
return state + 1
else: # if act == "exit":
return state - 1
def atoms(self):
"""
We care about two properties: whether the parking lot is "empty" or it is "full".
Hence, we will define two atomic propositions.
"""
return ["empty", "full"]
def label(self, state):
"""
The parking lot is empty if count is 0, and it is full when count is equal to num_slots.
"""
if state == 0:
return ["empty"]
elif state == self.num_slots:
return ["full"]
else:
return []
Note that our class definition closely follows the mathematical definition of a transition system. This provides an easy interface to implement various real-life or research paper based transition sysetsm models directly.
Moreover, the ability to define parameters such as num_slots
allows us to define a family of parameterized transition systems. This is particularly useful when running batch simulations.
However, the above style of defining transition system is inefficient. Especially, when running planning algorithms on large transition system, a large number of calls are made to states
and delta
function. Since there is non-trivial computation occurring in these functions, it slows the algorithms.
In such cases, it is efficient to use discrete graph representation of the transition system. We provide an easy interface to construct and visualize the equivalent graph representation of the defined transition system. This involves three steps:
- Instantiate your derived transition system class.
- Graphify the instance.
- Save to PNG the graph to a PNG for visualization.
Note: Graphs with more than 500 nodes cannot be saved to PNG. At present the best way to
visualize such graphs is to serialize
them and manually check them.
We demonstrate these steps next.
Instantiate CarParking¶
This is same as instantiating any class in Python.
tsys = CarParking(num_slots=5)
In case the initial state of the transition system is known, it can be set by calling initialize
function as shown below. In our case, assume that parking lot starts empty.
The initial state of the transition system can be checked using init_state
property.
tsys.initialize(0)
print("init_state:", tsys.init_state())
init_state: 0
Graphify¶
The graphify
function returns a ggsolver.graph.Graph
object that represents a multi-digraph. See Graph Class API and Graph Class Example for more information about Graph class.
Note: If any changes are made to the transition system after a call to graphify, the changes will not reflect in the graph. The graphify
must be called again.
graph = tsys.graphify()
[SUCCESS] <Graph with |V|=6, |E|=12> generated.
Visualize using to_png function¶
A graph with less than 500 nodes can be visualized using to_png
function. The to_png
function requires one positional argument:
fpath
: Path to where the generated PNG should be stored.
# Define path where to save the generated PNG.
fpath = "out/car_parking_nolabels.png"
# Generate a PNG
graph.to_png(fpath)
# Show PNG in Jupyter Notebook
html = img2html(fpath)
IPython.display.HTML(html)
As you may notice, the generated PNG has the structure as expected. However, it would be nice to be visualize what atomic propositions hold in which state and which edges correspond to which actions.
For this purpose, we pass two optional arguments to to_png
function.
# Define path where to save the generated PNG.
fpath = "out/car_parking_labeled.png"
# Generate a PNG
graph.to_png(fpath, nlabel=["state", "label"], elabel=["input"])
# Show PNG in Jupyter Notebook
html = img2html(fpath)
IPython.display.HTML(html)
Serialization¶
The graph of a transition system can be serialized into a dictionary. This allows us to save it or share it easily over a communication channel.
See Graph.Serialize() API Documentation to understand the components of generated dictionary.
graph.serialize()
{'graph': {'nodes': 6, 'edges': {0: {1: 1, 0: 1}, 1: {2: 1, 0: 1}, 2: {3: 1, 1: 1}, 3: {4: 1, 2: 1}, 4: {5: 1, 3: 1}, 5: {5: 1, 4: 1}}, 'node_properties': {'state': {'default': None, 'dict': {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5}}, 'label': {'default': None, 'dict': {0: ['empty'], 1: [], 2: [], 3: [], 4: [], 5: ['full']}}}, 'edge_properties': {'prob': {'default': None, 'dict': []}, 'input': {'default': None, 'dict': [{'edge': (0, 1, 0), 'pvalue': 'enter'}, {'edge': (0, 0, 0), 'pvalue': 'exit'}, {'edge': (1, 2, 0), 'pvalue': 'enter'}, {'edge': (1, 0, 0), 'pvalue': 'exit'}, {'edge': (2, 3, 0), 'pvalue': 'enter'}, {'edge': (2, 1, 0), 'pvalue': 'exit'}, {'edge': (3, 4, 0), 'pvalue': 'enter'}, {'edge': (3, 2, 0), 'pvalue': 'exit'}, {'edge': (4, 5, 0), 'pvalue': 'enter'}, {'edge': (4, 3, 0), 'pvalue': 'exit'}, {'edge': (5, 5, 0), 'pvalue': 'enter'}, {'edge': (5, 4, 0), 'pvalue': 'exit'}]}}, 'graph_properties': {'inp_domain': ['enter', 'exit'], 'is_probabilistic': False, 'actions': ['enter', 'exit'], 'is_deterministic': True, 'init_state': 0, 'atoms': ['empty', 'full']}}}