Evaluator: From the visualizer, you can use the Evaluator to look at relations in the given instance.
For example, in our Tic Tac Toe model, we can type “places” into the Evaluator, and it will show us the relation of all places in the current model. It has type Board -> Index -> Index -> Player
because it includes all places, not for one particular board.
Note: in the visualizer, click Theme and then check the box that says Use original atom names. This will make the visualizer show the $0, $1, etc for each atom, which will be helpful when you are looking at the visualizer in combination with the Evaluator.
Dot operator:
Board$0.places
will give us all of the places on Board$0
Board.places
will give us all of the places on all of the boards
Board.places.X
will give us all of the places where X has played on any board
Dot operator is similar to a join operation in databases
Join looks for equalities in the rightmost column of the first table and the leftmost column of the second table, and gives a relation for all of the rows that match.
Example:
|(One, Two, O) | [X]
|(Three, One, X) |
--> [(Three, One)]
(Board.places.X).(Board.places.X)
Board.places.X
has type Index -> Index
so a dot join between them has the correct type.
Board.places.X = {One -> One, One -> Three, Three -> One, Two -> Three}
(Board.places.X).(Board.places.X) = {One -> One, One -> Three, One -> Two, Three -> One, Three -> Two, Two -> One}
Why is this useful?
This is really useful for graph relations, for example, finding reachability in a graph. Consider this example:
sig Node {
edges : set Node
}
run { #Node = 3}
[N1] -> [N0] -> [N3] (N1 also has a self loop)
Note: = is used to show what the Evaluator evaluates to for each input
N = {N0, N1, N2}
edges = {N0->N2, N1->N0, N1->N1}
N.edges = {N0, N1, N2}
edges.N = {N0, N1}
edges.edges = {N1->N0, N1->N1, N1->N2} -- 2-hop paths
edges - iden = {N0->N2, N1->N0} -- removes cycles
iden = {N0->N0, N1->N1, N2->N2} -- a relation from every atom to itself (built in)
(edges - iden).(edges - iden) = {N1->N2} -- 2-hop paths not including cycles
edges.(edges - iden) = {N1->N0, N1->N2} -- 2-hop paths not taking a self-loop for the second step
N.N = type error because it would result in a zero column table
edges.edges - iden = {N1->N0, N1->N2} -- 2-hop paths that do not start and end at the same place
How does iden
know what type you want?
It doesn’t. iden
includes every type in your model, which is okay if you’re doing something like set-difference, but it can sometimes get tricky because it can sometimes add things in that you aren’t expecting.
Can we construct the subset of iden
including just the type we want?
Index.iden = {One, Three, Two}
isn’t quite what we want, because it’s not a relation from each index to itself anymore
Index -> Index = {One->One, One->Two, One->Three, Two->One, Two->Two, Two->Three, Three->One, Three->Two, Three->Three}
Index->Index & iden = {One->One, Two->Two, Three->Three}
&
: set intersection
+
: set union
-
: set difference
univ
: set containing all atoms in your model
iden
: relation from each atom to itself
~
: switches the rows and columns in a two-column table
Limits of Alloy:
There are infinitely many possible directed acyclic graphs, so Alloy can’t possibly look at all of them. Instead, it looks at all possible graphs within some constraint like number of nodes or number of edges or something.
Alloy always implicitly limits bounds to 3 for each sig type. You can override this by saying run {} for 5
, which will limit to 5 of each sig. This also applies to integers, so you can run into integer overflow problems