The predefined ordering module

Alloy has some predefined modules in a special util namespace. One of them is util/ordering, a parametrized module that can be used to impose a total order in a signature. In particular, this module defines a next relation that given an atom returns the next atom in the total order, and two derived singleton signatures first and last with the first and last atoms of the order, respectively. Note that last is the only atom without a next atom in the order. Module util/ordering also defines several derived predicates and functions to compare and compute the maximum or minimum of sets of elements (for example, gt is a predicate that can be used to compare if one atom is greater than another, and max is a function that computes the maximum of a given set of atoms).

Declaring totally ordered signatures

For example, let’s suppose we wanted to enrich our file system model with the creation timestamp of each entry. Since we will want to compare timestamps, we could first declare a totally ordered signature Time as follows.

open util/ordering[Time]
sig Time {}

The definition of signature Entry could now be changed to include the creation timestamp attribute.

sig Entry {
  object : one Object,
  name   : one Name,
  time   : one Time
}

Of course, we should impose a new constraint stating that the creation timestamp of an entry should precede that of its children.

fact children_timestamp {
  // The timestamp of an entry precedes that of its children
  all e : Entry, t : e.object.entries.time | lt[e.time, t]
}

Note the use of a chain of compositions to get all timestamps of all entries contained in the object of an entry. If the object is a file this set will be obviously empty and the constraint holds trivially. If it is a directory, then the timestamp of the respective entry must be less than those of all its children.

If we run the example command with a scope of 3, and customize the theme to show timestamps as attributes, we could get the following example filesystem.

Get the code to generate this instance.

You can see in the visualization relations first, last, and next introduced by module util/ordering. If we are not interested in seeing these relations in the visualization, we can omit them. In principle, this would make it very difficult to understand how the timestamps are ordered, and check if instances are valid. However, the module util/ordering is handled in special way be the Alloy Analyzer, which will attempt to name the inhabitants of the ordered signature sequentially according to the order defined by next. So you can see in our instance that Time0 is the first, which is followed by Time1, and then by Time3, which is the last. If we really want to confirm the value of the next relation we could go to the evaluator and ask for its value. However, we will get an ambiguity error since next is also defined in the util/integer module that is imported by default. To disambiguate we could type the qualified name ordering/next instead.

Get the code to generate this instance.

To see what other functions and derived relations are declared in util/ordering you can open it in the menu File ‣ Open Sample Models….

The above fact, imposing that the creation timestamp of an entry should precede that of its children, actually makes fact no_indirect_containment redundant, since it is no longer possible for directories to contain themselves directly or indirectly. We can confirm this by changing that fact to an assertion and using the Analyzer to verify it.

assert no_indirect_containment {
   // Directories cannot contain themselves directly or indirectly
   all d : Dir | d not in descendants[d]
}
check no_indirect_containment for 6

Analysis scopes

Besides naming the atoms according to the total order, module util/ordering has another peculiarity concerning the analysis. The scope of the signature that is passed as parameter to util/ordering is always exact even if not declared with exactly. The reason for this is to make analysis faster, but one should always be aware of this detail when running commands, as it can sometimes introduce inconsistencies, making run commands trivially unsatisfiable or check commands trivially valid.

As an example, consider the following (rather arbitrary) model of a finite sequence of natural numbers, distinguishing between even and odd numbers, and requiring the first in the sequence to be even and the last to be odd.

open util/ordering[Nat]

abstract sig Nat {}
sig Even, Odd extends Nat {}

fact {
  all n : Even | n.next in Odd
  all n : Odd | n.next in Even
  first in Even
  last in Odd
}

run example {}

If we execute the command example we get no instance! This is due to the fact that the default scope for Nat is 3, and since this signature is being totally ordered with util/ordering this scope is required to be exact. Unfortunately, with exactly 3 atoms in Nat it is impossible to satisfy the given constraints. If we change the scope of example to 4 then we will obtain one instance.

Get the code to generate this instance.

This is also an example where the naming of the atoms does not help in understanding what is the total order, since the ordered signature Nat is abstract and extended by Even and Odd. If you omit the util/ordering relations from the visualization, you can always use the evaluator to consult the atom order.

Get the code to generate this instance.

As a last note on scopes, for efficiency reasons the util/ordering module also requires the parameter signature to be non-empty (it forces first and last to always contain exactly one atom), meaning that setting its scope to 0 will also result in an inconsistent specification.