I loaded your spec with the Toolbox, had it instantiate the constant
`vertices' with a set {n1, n2, n3} of model values, and ran TLC on the
spec. It reported the error
TLC encountered a non-enumerable quantifier bound
Seq({n1, n2, n3}).
line 18, col 38 to line 18, col 38 of module Test
where the location was the first S in the definition of
existsUniqueIn(S, P(_))
If you read the section in Specifying Systems on how TLC checks a
spec, you should realize that evaluating isTree requires TLC to
enumerate the infinite set Seq(vertices).
The simplest way to fix this is to modify the definition of isTree so it
applies existsUniqueIn to a finite set of sequences--for example
sequences of length at most equal to the cardinality of `vertices'.
Your approach of defining a spec with a stuttering next-state action
works. However, you can accomplish the same thing by not having
any variables or behavior spec, and simply having the Toolbox
tell TLC to evaluate the constant formula
\A root \in vertices, edges \in SUBSET SUBSET vertices
/\ (\A e \in edges : Cardinality(e) = 2)
/\ isTree(edges, root)
/\ PrintT("tree:") /\ PrintT(<< edges, root >>)
However, my guess is that you'll run TLC out of memory with a set
`vertices' containing more than 3 nodes.
Personally, I have found it most convenient to define a tree to
be a set of vertices together with a function mapping each non-root
node to its parent. However, what works best depends on what you're
going to use the definition for.
First of all, I'm sorry for the silly suggestion I made. The formula
I suggested will probably just evaluate to FALSE and print nothing. A
formula that works is:
\A root \in vertices,
edges \in SUBSET SUBSET vertices :
IF /\ (\A e \in edges : Cardinality(e) = 2)
/\ isTree(edges, root)
THEN PrintT("tree:") /\ PrintT(<< edges, root >>)
ELSE TRUE
If I try the corrected spec with `vertices' a set of two elements,
the spec works fine and produces the correct output
"tree:"
<<{{n1, n2}}, n1>>
"tree:"
<<{{n1, n2}}, n2>>
So, the stack overflow is probably legitimate. However, I don't
understand why TLC should produce a stack overflow when evaluating
your Init predicate when it doesn't have a problem evaluating the
constant expression above for three vertices. I will investigate
that further.
On evaluating the formula
\A root \in vertices, edges \in SUBSET SUBSET vertices :
/\ (\A e \in edges : Cardinality(e) = 2)
\* /\ isTree(edges, root)
/\ PrintT("tree:")
/\ PrintT(<< edges, root >>)
TLC is doing what it should, except that the
<<"$!@$!@$!@$!@$!", FALSE>>
should not have appeared in the output and the Toolbox should have
reported FALSE as the value of the expression. I believe this is a
bug that has been fixed; the fix will appear in the next release. The
particular output that it produced is caused by { } (the empty set)
being the first value for `edges' that TLC tried when evaluating the
universal quantification. You will learn more by figuring out for
yourself why that's the case than by having me explain it.
Here is the explanation of why a stack overflow occurs when evaluating
the initial predicate Init but not when evaluating the "equivalent"
constant formula.
First of all, if you tell Java to use a 2 Mbyte stack, the model runs
fine and there is no stack overflow. Currently, there is no way to
specify the stack size when running TLC from the Toolbox. There
should be and there will be in a future release.
Now for the explanation. TLC expands all quantifiers as conjunctions
or disjunctions. It also effectively translates an implication into a
disjunction. If you perform this expansion, you will see that for a
model with 9 nodes, evaluating isTree(edges, root) requires evaluating
a conjunction of the form
(*) (A_1 \/ B_1) /\ ... /\ (A_1600 \/ B_1600)
where the \/ comes from the => in the definition of existsUnique.
With the usual recursive procedure for expression evaluation, in the
worst possible case (each A_i false and each B_i true), the recursion
would reach depth 1600 when evaluating (*). This worst case
apparently never happens, and ordinary evaluation works fine.
However, if you understand how TLC evaluates the set of intial states,
you will realize that whenever evaluation of Init requires evaluating
an expression of the form A \/ B, TLC has to explore both
possibilities to find the set of all states satisfying Init.
Therefore, TLC always goes to depth 1600 when evaluating expression
(*) as part of the initial predicate. Hence the overflow. (In this
particular case, it's not necessary to do this because the preceding
conjuncts of Init have determined the values of all variables, but TLC
doesn't handle this case specially.)