(* Example taken from https://github.com/tlaplus/Examples/blob/master/specifications/echo/Echo.tla *)
-------------------------------- MODULE Echo --------------------------------
(***************************************************************************)
(* The Echo algorithm for constructing a spanning tree in an undirected *)
(* graph starting from a single initiator, as a PlusCal algorithm. *)
(***************************************************************************)
EXTENDS Naturals, FiniteSets, Relation, TLC
CONSTANTS Node, \* set of nodes
initiator, \* single initiator, will be the root of the tree
R \* neighborhood relation, represented as a Boolean function over nodes
ASSUME /\ initiator \in Node
/\ R \in [Node \X Node -> BOOLEAN]
\* No edge from a node to itself (self-loops).
/\ IsIrreflexive(R, Node)
\* Undirected graph (there exists an edge from b
\* to a if there exists an edge from a to b).
/\ IsSymmetric(R, Node)
\* There exists a spanning tree consisting of *all* nodes.
\* (no forest of spanning trees).
/\ IsConnected(R, Node)
NoNode == CHOOSE x : x \notin Node
neighbors(n) == { m \in Node : R[m,n] }
(**
--algorithm Echo {
variable inbox = [n \in Node |-> {}]; \* model communication between nodes
define { \* sending and receiving messages
\* network obtained from net when p sends message of kind knd to q
send(net, p, q, knd) == [net EXCEPT ![q] = @ \cup {[kind |-> knd, sndr |-> p]}]
\* network obtained from net when p receives a message
receive(net, p, msg) == [net EXCEPT ![p] = @ \ {msg}]
\* network obtained from net when p send message of kind knd to all nodes in dest
multicast(net, p, dest, knd) ==
[m \in Node |-> IF m \in dest THEN net[m] \cup {[kind |-> knd, sndr |-> p]}
ELSE net[m]]
}
process (node \in Node)
variables parent = NoNode,
children = {},
rcvd = 0,
nbrs = neighbors(self); {
n0: if (self = initiator) {
\* initiator sends first message to all its neighbors
inbox := multicast(inbox, self, nbrs, "m")
};
n1: while (rcvd < Cardinality(nbrs)) {
\* receive some message from a neighbor
with (msg \in inbox[self],
net = receive(inbox, self, msg)) {
rcvd := rcvd+1;
if (self # initiator /\ rcvd = 1) {
assert(msg.kind = "m"); \* the first received message is always of type "m"
\* note the sender as the node's parent and send an "m" message to all remaining neighbors
parent := msg.sndr;
inbox := multicast(net, self, nbrs \ {msg.sndr}, "m")
}
else {
\* subsequent messages are counted but don't give rise to another message
inbox := net
};
if (msg.kind = "c") { children := children \cup {msg.sndr} }
} \* end with
}; \* end while
n2: if (self # initiator) {
\* when non-initiator has received messages from all neighbors, acknowledge
\* child relationship to the parent
assert(parent \in nbrs);
inbox := send(inbox, self, parent, "c")
}
} \* end process
}
**)
\* BEGIN TRANSLATION
VARIABLES inbox, pc
(* define statement *)
send(net, p, q, knd) == [net EXCEPT ![q] = @ \cup {[kind |-> knd, sndr |-> p]}]
receive(net, p, msg) == [net EXCEPT ![p] = @ \ {msg}]
multicast(net, p, dest, knd) ==
[m \in Node |-> IF m \in dest THEN net[m] \cup {[kind |-> knd, sndr |-> p]}
ELSE net[m]]
VARIABLES parent, children, rcvd, nbrs
vars == << inbox, pc, parent, children, rcvd, nbrs >>
ProcSet == (Node)
Init == (* Global variables *)
/\ inbox = [n \in Node |-> {}]
(* Process node *)
/\ parent = [self \in Node |-> NoNode]
/\ children = [self \in Node |-> {}]
/\ rcvd = [self \in Node |-> 0]
/\ nbrs = [self \in Node |-> neighbors(self)]
/\ pc = [self \in ProcSet |-> "n0"]
n0(self) == /\ pc[self] = "n0"
/\ IF self = initiator
THEN /\ inbox' = multicast(inbox, self, nbrs[self], "m")
ELSE /\ TRUE
/\ inbox' = inbox
/\ pc' = [pc EXCEPT ![self] = "n1"]
/\ UNCHANGED << parent, children, rcvd, nbrs >>
n1(self) == /\ pc[self] = "n1"
/\ IF rcvd[self] < Cardinality(nbrs[self])
THEN /\ \E msg \in inbox[self]:
LET net == receive(inbox, self, msg) IN
/\ rcvd' = [rcvd EXCEPT ![self] = rcvd[self]+1]
/\ IF self # initiator /\ rcvd'[self] = 1
THEN /\ Assert((msg.kind = "m"),
"Failure of assertion at line 50, column 16.")
/\ parent' = [parent EXCEPT ![self] = msg.sndr]
/\ inbox' = multicast(net, self, nbrs[self] \ {msg.sndr}, "m")
ELSE /\ inbox' = net
/\ UNCHANGED parent
/\ IF msg.kind = "c"
THEN /\ children' = [children EXCEPT ![self] = children[self] \cup {msg.sndr}]
ELSE /\ TRUE
/\ UNCHANGED children
/\ pc' = [pc EXCEPT ![self] = "n1"]
ELSE /\ pc' = [pc EXCEPT ![self] = "n2"]
/\ UNCHANGED << inbox, parent, children, rcvd >>
/\ nbrs' = nbrs
n2(self) == /\ pc[self] = "n2"
/\ IF self # initiator
THEN /\ Assert((parent[self] \in nbrs[self]),
"Failure of assertion at line 65, column 10.")
/\ inbox' = send(inbox, self, parent[self], "c")
ELSE /\ TRUE
/\ inbox' = inbox
/\ pc' = [pc EXCEPT ![self] = "Done"]
/\ UNCHANGED << parent, children, rcvd, nbrs >>
node(self) == n0(self) \/ n1(self) \/ n2(self)
(* Allow infinite stuttering to prevent deadlock on termination. *)
Terminating == /\ \A self \in ProcSet: pc[self] = "Done"
/\ UNCHANGED vars
Next == (\E self \in Node: node(self))
\/ Terminating
Spec == Init /\ [][Next]_vars
Termination == <>(\A self \in ProcSet: pc[self] = "Done")
\* END TRANSLATION
(***************************************************************************)
(* Correctness properties. *)
(***************************************************************************)
TypeOK ==
/\ parent \in [Node -> (Node \cup {NoNode})]
/\ children \in [Node -> SUBSET Node]
/\ rcvd \in [Node -> Nat]
/\ nbrs \in [Node -> SUBSET Node]
/\ \A n \in Node : nbrs[n] = neighbors(n) /\ rcvd[n] <= Cardinality(nbrs[n])
/\ inbox \in [Node -> SUBSET [kind : {"m","c"}, sndr : Node]]
/\ \A n \in Node : \A msg \in inbox[n] : msg.sndr \in nbrs[n]
(* The initiator never has a parent *)
InitiatorNoParent == parent[initiator] = NoNode
(* If a node has a parent, it is a neighbor node *)
ParentIsNeighbor == \A n \in Node : parent[n] \in neighbors(n) \cup {NoNode}
(* A node n is a child of node m only if m is the parent of n.
At the end of the computation, this is "if and only if". *)
ParentChild == \A m,n \in Node :
/\ n \in children[m] => m = parent[n]
/\ m = parent[n] /\ pc[m] = "Done" => n \in children[m]
(* Compute the ancestor relation *)
IsParent == [m,n \in Node |-> n = parent[m]]
IsAncestor == TransitiveClosure(IsParent, Node)
(* At the end of the computation, the initiator is an ancestor of every other node
and the ancestor relation is acyclic.
Beware: evaluating this property over any but tiny graphs is costly.
*)
AncestorProperties ==
(\A n \in Node : pc[n] = "Done")
=> LET anc == IsAncestor
IN /\ \A n \in Node \ {initiator} : anc[n, initiator]
/\ IsIrreflexive(anc, Node)
=============================================================================
\* Modification History
\* Last modified Wed Jun 17 09:23:17 PDT 2020 by markus
\* Last modified Sun Jun 14 17:11:39 CEST 2020 by merz
\* Created Tue Apr 26 09:42:23 CEST 2016 by merz