• Keine Ergebnisse gefunden

Implementing the Search: A Specific State

Im Dokument CAVA – A Verified Model Checker  (Seite 54-57)

After laying the foundation by specifying a generic search and a generic parameterized search, we are now continuing by introducing an explicit search instantiation. While this is more concrete than the generic versions, it is still aimed to be as abstract as possible by not caring about implementation details. Its goals are mainly to provide the final means to base an explicit DFS-using algorithm on, and to provide an extensive library of invariants. Therefore, this stage will be called theabstract level(opposed to thegeneric level we described so far, and the concrete (implementation) level which is still to come (Sections 4.5, 4.5.3)).

For an increased usability, one goal for specifiying this abstract level is to gather as much information as possible throughout the search process. This enables us to provide detailed lemmas about the search, which in turn may come in useful for the embedded algorithm.

This is made possible by recent enhancements of the Isabelle Refinement Framework [31]

which allows to remove information when projecting the abstract state onto the concrete state (details follow in Section 4.5.1), thereby enabling us to generate lean code where the auxiliary data from the abstract level is no longer present.

The content of the state, i. e., the information gathered, then can be divided into three categories:

Management information This includes those fields which are essential for conducting the search at all. This category consists of the search stack and the set of pending edges. While the first one is natural, the second one can be represented in multiple ways. One option, which was for example chosen in the first approach [35], is to use another stack, where each item represents the pending edges of the corresponding node on the search stack. Unfortunately, proving and enforcing this correspondence is rather inelegant and clutters the theory, as will be described in Section 4.8.1. A similar solution where both stacks are combined into one is, again, rather inelegant on the abstract level, as it needlessly merges two different concepts, but is a viable

4.3 Implementing the Search: A Specific State

option to be used in the implementation. The choice for the abstract level then is indeed the simple set ofall the pending edges. While this would be a nightmare performance-wise in any implementation it is just right for abstract reasoning, as the pending edges of any nodeucan be simply queried bypending ``{u}.

Timing information A natural, and necessary, field to have in a depth-first search is the set ofdiscoverednodes. A generalization is to not only mark the fact of discovery, but also the order. This information allows advanced reasoning as will be shown later on by the Parenthesis Theoremand theWhite Path Theorem, which in turn are needed for the correctness proof of Tarjan’s algorithm as pictured in Section 4.7. Similarly, one adds the timing information for backtracking from a node, i. e.,finishednodes.

To enable gathering such information, one needs to also keep track of the current time, therefore acounterhas to be added.

Search tree This category keeps track of the edges which have been traversed, differen-tiating between forward, cross, and back edges (cf. Section 4.1). While they can be generated from the timing information, we gather them explicitly. This ensures that there is no uncertainty about them representing the correct information. Also, due to the aforementioned ability to safely project the state, such additional information comes for free.

Combined, the state is thus defined as follows:

record'v state = counter :: nat

discovered :: 'v⇒nat option finished :: 'v⇒nat option pending :: 'v rel

stack :: 'v list tree :: 'v rel back_edges :: 'v rel cross_edges :: 'v rel

Now, to help further use, we can already instantiate the type variable for the state in gen_parameterizationto get a shorter type for parameterizations using ourstate:

type_synonym('v,'es)parameterization

=('v,('v,'es)state_scheme, 'es)gen_parameterization

We then start to specify depth-first search. The search needs two parameters: a graph, and the parameterization. We formalize graphs as a set of edgesE and a set of initial nodesV01:

localeparam_DFS = fb_graph E V0 forE :: 'v relandV0:: 'v set +

fixesparam ::('v,'es)parameterization

Inside this locale, the implementations for the search procedure are provided, using the just definedstate. Thus, the discovery of a new node is implemented as:

1Thefb_graphlocale adds the assumption of the graph beingfinitely branching, i. e., each node having only a finite number of successors. This is a technical detail needed for being able to reason aboutforeachloops.

definition(inparam_DFS)discover :: 'v⇒'v⇒'v state⇒'v state where

discover u v s≡let

d =(discovered s)(v7→counter s); c = counter s + 1;

st = v#stack s;

p = pending s∪ {v} ×E``{v}; t = insert(u,v) (tree s)

insLdiscovered := d, counter := c, stack := st, pending := p, tree := tM

This implementation should not be surprising, as it updates the fields of the state in the expected way:

• record the discovery time of the new node and increase the timing counter

• push the new node onto the stack

• add all outgoing edges of the new node to the set of pending edges

• record the just taken edge (uis the predecessor ofv) as forward edge The other operations follow in a similar manner and will not be listed here.

Using those operations, we can finally define an instance ofgen_basic_dfs_struct, where each of the fields of the struct is represented by an operation:

definition(inparam_DFS)gbs≡L gbs_init = λe.return(empty_state e), gbs_is_empty_stack = is_empty_stack,

gbs_discover =λu v s.return(discover u v s), (* rest of fields omitted *)

M

From this and the provided parameterization, it can then be shown thatparam_DFSis indeed a sublocale ofgen_param_dfs: sublocalegen_param_dfs gbs param V0.

Example 4.3.1(Cyclicity Checker continued)

From Example 4.2.1 on page 40, we recall our definition of the cyclicity checker as a parametrization to the DFS (fields omitted areNOOP):

definitioncyc_checkerwhere cyc_checker =L

on_init≡returnLcyc = FalseM, (* initially no cycle has been found *), on_back_edge≡λu v s.returnLcyc = TrueM (* cycle! *),

is_break≡λs. cyc s(* break iff cycle has been found *) M

We now instantiate our newly defined parameterized DFS with our formalization of cyclicity checking:

interpretationcyc!: param_DFS E V0cyc_checkerforE V0.

As a result, we now have a fully initiated depth-first search: the search is completely specified byparam_DFS, and the extension bycyc_checker. This enables us to refer to the final cyclicity checker bycyc.dfs.

Im Dokument CAVA – A Verified Model Checker  (Seite 54-57)