-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1583 from informalsystems/add-Dijkstra-example
Add Dijkstra's Stabilizing Token Ring example in Quint
- Loading branch information
Showing
6 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# EWD426 | ||
|
||
This folder contains Quint specifications for the three different self-stabilization algorithms proposed by Dijkstra in [EWD426](https://www.cs.utexas.edu/~EWD/transcriptions/EWD04xx/EWD426.html). | ||
1. A solution with K-state machines [ewd426.qnt](ewd426.qnt) | ||
2. A solution with three-state machines [ewd426_3.qnt](ewd426_3.qnt) | ||
3. A solution with four-state machines [ewd426_4.qnt](ewd426_4.qnt) | ||
|
||
Due to the presence of temporal properties and fairness, we need to use TLC to model check these specs. As the integration with TLC is not completed, we provide a script to automate running it for these specific specifications: [check_with_tlc.sh](check_with_tlc.sh). | ||
|
||
If you are trying to learn/understand these algorithms, we recommend playing with the Quint REPL. For that, pick one of the files (for example [ewd426.qnt](ewd426.qnt)) and run the following command in the terminal: | ||
``` sh | ||
quint -r ewd426.qnt::ewd426 | ||
``` | ||
|
||
This will open the REPL with the `ewd426` machine loaded. You can now interact with the machine and explore its states and transitions: | ||
|
||
``` bluespec | ||
Quint REPL 0.22.4 | ||
Type ".exit" to exit, or ".help" for more information | ||
>>> init | ||
true | ||
>>> step | ||
true | ||
>>> show_token(system) | ||
Map(0 -> false, 1 -> true, 2 -> true, 3 -> false, 4 -> true, 5 -> false) | ||
>>> 5.reps(_ => step) | ||
true | ||
>>> show_token(system) | ||
Map(0 -> true, 1 -> false, 2 -> false, 3 -> false, 4 -> false, 5 -> false) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#!/bin/bash | ||
|
||
# Files to process | ||
FILES=("ewd426.qnt" "ewd426_3.qnt" "ewd426_4.qnt") | ||
|
||
# Apalache jar path | ||
APALACHE_JAR="$HOME/.quint/apalache-dist-0.47.2/apalache/lib/apalache.jar" | ||
|
||
# Memory options for Java | ||
JAVA_OPTS="-Xmx8G -Xss515m" | ||
|
||
for file in "${FILES[@]}"; do | ||
base_name="${file%.qnt}" | ||
tla_file="${base_name}.tla" | ||
cfg_file="${base_name}.cfg" | ||
|
||
echo "Processing $file..." | ||
|
||
# Step 1: Compile the .qnt file to .tla | ||
quint compile --target=tlaplus "$file" --temporal=convergence,closure,persistence --invariant=tokenInv > "$tla_file" | ||
|
||
if [[ $? -ne 0 ]]; then | ||
echo "Error: Compilation failed for $file" | ||
exit 1 | ||
fi | ||
|
||
# Step 2: Fix init to be a predicate instead of an assignment | ||
perl -0777 -i -pe "s/(.*)'\s+:= (.*_self_stabilization_.*?initial)/\1 = \2/s" "$tla_file" | ||
|
||
|
||
if [[ $? -ne 0 ]]; then | ||
echo "Error: Failed to edit $tla_file" | ||
exit 1 | ||
fi | ||
|
||
# Step 3: Create the .cfg file | ||
cat <<EOF > "$cfg_file" | ||
INIT | ||
q_init | ||
NEXT | ||
q_step | ||
PROPERTY | ||
q_temporalProps | ||
INVARIANT | ||
q_inv | ||
EOF | ||
|
||
# Step 4: Run TLC with the required Apalache lib files and check output | ||
output=$(java $JAVA_OPTS -cp "$APALACHE_JAR" tlc2.TLC -deadlock "$tla_file" 2>&1) | ||
|
||
# Step 5: Delete the generated .tla and .cfg files | ||
rm -f "$tla_file" "$cfg_file" | ||
|
||
if echo "$output" | grep -q "Model checking completed. No error has been found."; then | ||
echo "Model checking succeeded for $tla_file" | ||
else | ||
echo "Error: Model checking failed for $tla_file" | ||
echo "$output" | ||
exit 1 | ||
fi | ||
|
||
done | ||
|
||
echo "All files processed successfully." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// -*- mode: Bluespec; -*- | ||
/** | ||
* ewd426's Stabilizing Token Ring (EWD426) | ||
* K state machine | ||
* This implementation ensures that from some time on, | ||
* exactly one token circulates in a set of nodes, | ||
* | ||
* Mahtab Norouzi, Josef Widder, Informal Systems, 2024-2025 | ||
*/ | ||
module self_stabilization { | ||
// Number of nodes in the ring | ||
const N: int | ||
const K: int | ||
|
||
/// Ensures the state space is larger than the number of nodes | ||
assume _ = K >= N | ||
|
||
val bottom = 0 | ||
val top = N | ||
|
||
/// Mapping of node indices to their states | ||
var system: int -> int | ||
|
||
/// Check if a node has the token | ||
pure def has_token(nodes: int -> int, index: int): bool = | ||
if (index == bottom) | ||
nodes.get(bottom) == nodes.get(top) | ||
else | ||
not(nodes.get(index) == nodes.get(index - 1)) | ||
|
||
/// Update the state of a specific node | ||
pure def state_transition(nodes: int -> int, index: int): int = | ||
if (not(has_token(nodes, index))) | ||
nodes.get(index) | ||
else if (index == bottom) | ||
(nodes.get(bottom) + 1) % K | ||
else | ||
nodes.get(index - 1) | ||
|
||
/// Initialize all nodes with non-deterministic states | ||
action init = all { | ||
nondet initial = 0.to(N).setOfMaps(0.to(K - 1)).oneOf() | ||
system' = initial | ||
} | ||
|
||
/// Pick a single active node non-deterministically and update its state | ||
action step = { | ||
nondet node = 0.to(N).filter(i => has_token(system, i)).oneOf() | ||
system' = system.set(node, state_transition(system, node)) | ||
} | ||
|
||
/// Pick several active nodes non-deterministically and update their state. | ||
/// Closer to the distributed demon is discussed in EWD 391. We are not | ||
/// considering interleaving in the execution of state_transition here | ||
action distributed_step = { | ||
nondet nodes = 0.to(N).filter(i => has_token(system, i)).powerset().exclude(Set()).oneOf() | ||
system' = nodes.fold(system, (s, x) => s.set(x, state_transition(system, x))) | ||
} | ||
|
||
// Pure function to count how many tokens exist | ||
pure def count_tokens(nodes: int -> int): int = { | ||
0.to(N).filter(i => has_token(nodes, i)).size() | ||
} | ||
|
||
// Temporal properties | ||
temporal convergence = step.weakFair(Set(system)) implies eventually(count_tokens(system) == 1) | ||
temporal closure = always(count_tokens(system) == 1 implies always(count_tokens(system) == 1)) | ||
temporal persistence = step.weakFair(Set(system)) implies eventually(always(count_tokens(system) == 1)) | ||
|
||
// Invariant | ||
def tokenInv = count_tokens(system) > 0 | ||
|
||
/// to better see the token in the repl | ||
pure def show_token(nodes: int -> int): int -> bool = | ||
nodes.keys().mapBy(i => has_token(nodes, i)) | ||
} | ||
|
||
module ewd426 { | ||
import self_stabilization(N = 5, K = 7).* | ||
} | ||
|
||
module broken_ewd426 { | ||
// This should break the assumption of K >= N. See #1182. | ||
import self_stabilization(N = 3, K = 2).* | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// -*- mode: Bluespec; -*- | ||
/** | ||
* ewd426's Stabilizing Token Ring (EWD426) | ||
* 3 state machine | ||
* This implementation ensures that from some time on, | ||
* exactly one token circulates in a set of nodes. | ||
* | ||
* Mahtab Norouzi, Josef Widder, Informal Systems, 2024-2025 | ||
*/ | ||
module self_stabilization_three_state { | ||
// Number of nodes in the ring | ||
const N: int | ||
|
||
val bottom = 0 | ||
val top = N | ||
|
||
// Variable: Mapping of node indices to their states | ||
var system: int -> int | ||
|
||
pure def has_token(nodes: int -> int, index: int): bool = | ||
val s = nodes.get(index) | ||
if (index == bottom) | ||
val r = nodes.get(1) | ||
(s + 1) % 3 == r | ||
else if (index == top) | ||
val l = nodes.get(N - 1) | ||
val r = nodes.get(bottom) | ||
l == r and not((l + 1) % 3 == s) | ||
else | ||
val l = nodes.get(index - 1) | ||
val r = nodes.get(index + 1) | ||
or { | ||
(s + 1) % 3 == l, | ||
(s + 1) % 3 == r | ||
} | ||
|
||
// Transition function for the three-state machine | ||
pure def state_transition(nodes: int -> int, index: int): int = | ||
val s = nodes.get(index) | ||
if (not(has_token(nodes, index))) | ||
s | ||
else if (index == bottom) | ||
(s + 2) % 3 // (s - 1) % 3 | ||
else if (index == top) | ||
val l = nodes.get(N - 1) | ||
(l + 1) % 3 | ||
else | ||
val l = nodes.get(index - 1) | ||
val r = nodes.get(index + 1) | ||
if ((s + 1) % 3 == l) | ||
// Not 100% sure what ewd426 wants here. Might be non-determinism | ||
// between the two options. We make it deterministic here. | ||
if ((s + 1) % 3 == r) | ||
r | ||
else | ||
l | ||
else if ((s + 1) % 3 == r) | ||
r | ||
else | ||
s | ||
|
||
/// Initialize all nodes with random states | ||
action init = all { | ||
nondet initial = 0.to(N).setOfMaps(0.to(2)).oneOf() | ||
system' = initial | ||
} | ||
|
||
/// Pick a single active node non-deterministically and update its state | ||
action step = { | ||
nondet node = 0.to(N).filter(i => has_token(system, i)).oneOf() | ||
system' = system.set(node, state_transition(system, node)) | ||
} | ||
|
||
/// Pick several active nodes non-deterministically and update their state. | ||
/// Closer to the distributed demon is discussed in EWD 391. We are not | ||
/// considering interleaving in the execution of state_transition here | ||
action distributed_step = { | ||
nondet nodes = 0.to(N).filter(i => has_token(system, i)).powerset().exclude(Set()).oneOf() | ||
system' = nodes.fold(system, (s, x) => s.set(x, state_transition(system, x))) | ||
} | ||
|
||
// Pure function to count how many tokens exist | ||
pure def count_tokens(nodes: int -> int): int = { | ||
to(0, N).filter(i => has_token(nodes, i)).size() | ||
} | ||
|
||
// Temporal properties | ||
temporal convergence = step.weakFair(Set(system)) implies eventually(count_tokens(system) == 1) | ||
temporal closure = always(count_tokens(system) == 1 implies always(count_tokens(system) == 1)) | ||
temporal persistence = step.weakFair(Set(system)) implies eventually(always(count_tokens(system) == 1)) | ||
|
||
// Invariant | ||
def tokenInv = count_tokens(system) > 0 | ||
|
||
/// to better see the token in the repl | ||
pure def show_token(nodes: int -> int): int -> bool = | ||
nodes.keys().mapBy(i => has_token(nodes, i)) | ||
} | ||
|
||
module ewd426_3 { | ||
import self_stabilization_three_state(N = 5).* | ||
} |
Oops, something went wrong.