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