(* 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