Iterative Decomposition Framework¶
Imandra has the ability to enumerate the state space of a function via its decomposition feature.
IDF
is a framework that builds on top of that and adds the ability to enumerate the state space of a (possibly infinite) state machine after a bounded number of abstract transitions.
Let's define one such state machine and see how IDF
can help us navigating its state space: we must define a module conforming to Idf_intf.SM_SIG.
An additional constraint which is not enforciable by the OCaml type system but is enforced at runtime by IDF
is that the type of event
must be an algebraic variant type (or an alias to one).
module SM = struct
type i = Int of int | Zero
(* type of state machine events *)
type event = Add of i | Sub of int | Reset
(* type of state machine state *)
type state = int
(* initial state of the state machine *)
let init_state = 0
(* transition function of the state machine *)
let step event state =
if state = 1337 then
state
else
match event with
| Reset -> 0
| Add (Zero) -> state
| Add (Int n) ->
if n < 0 then
state
else if n = 0 || n + state > 9000 then
0
else
state + n
| Sub n ->
if n > state then
state
else
state - n
(* validity function for an event transition *)
let is_valid event state =
match event with
| Add (Int n) when state < 3 -> n > 50
| Add (Int 0) -> false
| Sub n -> (n > 0 && n <= state) || (n < 0)
| _ -> true
end
We can try to compute with this state machine:
SM.
(init_state
|> step (Add Zero)
|> step (Add (Int 15))
|> step Reset
|> step (Sub (-1)))
Now that we have a model to decompose, we must create a symbolic template of SM
events, by creating a module conforming to Idf_intf.TPL_SIG
Note that there's not necessarily a 1:1 correspondence between the variant constructors of SM.event
and the ones of TPL.t
, for example TPL.Any
is an empty mapping, meaning that no constructor will be constrained for that symbolic event, TPL.AddInt
on the other hand fixes the event to be a SM.Add (SM.Int _)
, thus excluding SM.Add SM.Zero
.
module TPL = struct
(* type of a symbolic state machine event *)
type t = Add | AddInt | Sub | Reset | Any
type c = SM.event
type state = SM.state
(* mapping function from a symbolic state machine event to a concrete event *)
let concrete t c _ = match t,c with
| Any, _ -> true
| Add, SM.Add _ -> true
| AddInt, SM.Add(SM.Int _) -> true
| Sub, SM.Sub _ -> true
| Reset, SM.Reset -> true
| _ -> false
end
Next we need to hook the SM
and TPL
modules into a module conforming to Idf_intf.DSM_SIG:
module Decomp = struct
module State_machine = SM
module Template = TPL
let module_name = "Decomp"
end
And finally we can instantiate Idf
using Decomp
:
We are now ready to decompose a SM
state machine under a particular TPL.t
template
We've invoked IDF.decompose
over a template of [Any;AddInt]
, which means we'll try to symbolically decompose the state space of a state machine after the following steps (_
is used to indicate a symbolic value):
SM.
(init_state
|> step _
|> step (Add (Int _)))
In order to consume concrete paths, we must extract them from the decomposition graph g
:
let first_path = IDF.paths g |> List.hd;;
let full_node = first_path |> CCList.rev |> CCList.hd
Now that we have a path
value available, we can explore what we can do with it:
- We can ask for sample event values for the current path, which will return a sample for each event in the path:
- We can ask for a sample execution replay of the current path, which will return a list of tuples of
input state * input event * output state
for each sample event in the path:
- We can ask for the concrete regions representing the constraints of the state machine up to each event in the path:
#install_printer Imandra_tools.Region_pp.print;;
let regions = List.map (fun n -> Remote_ref.get_shared_block (IDF.region n)) first_path
It is to be noted that the each symbolic event in those constraints is represented as the nth
element of the list e
, thus the first Any
event will be List.hd e
, the second AddInt
will be List.hd (List.tl e)
, and since we've asked IDF
to decompose a path of exactly two events, there will be no third or more events, and thus we can find a constraint to that effect: List.tl (List.tl e) = []
.