Asynchronous Composition with Parameterized Submodules

153 views
Skip to first unread message

William Schultz

unread,
Jul 26, 2024, 4:29:42 PMJul 26
to tlaplus
I am running into an issue related to how to specify the composition between a main, root module with several "child" modules that are used via parameterized instantiations. 

My overall goal is to have each instantiated child module be composed asynchronously with (i.e. can interleave with) the root module. I am facing an issue with writing and checking such a specification in practice, though, which can be illustrated with a small example as follows.

Assume we have a root module M, and a helper/child module C, where M and C look something like the following:

---- MODULE M ----

CONSTANT Node

VARIABLE mx
VARIABLE x_arr

Child(s) == INSTANCE C WITH x <- x_arr[s]

M1 == mx' = mx + 2

Init ==
  /\ mx = 0
  /\ x_arr = [n \in Node |-> 0]

Next ==
  \* Root module transition.
  \/ /\ M1
     /\ UNCHANGED x_arr
  \* Child transition.
  \/ /\ \E n \in Node : Child(n)!C1  
     /\ UNCHANGED mx

====

---- MODULE C ----

VARIABLE x
C1 == x' = x + 1
Init == x = 0
Next == C1

====

That is, inside M, we instantiate a set of C modules parameterized over 'CONSTANT Node' e.g. a set of process/node ids. And we define a variable x_arr \in [Node -> Nat] to store the internal variables for each of these instantiated child modules.

The problem, though, is that the transition relation of M as written above doesn't work in practice, since if we apply the substitution as defined for each module C, the underlying expression in the second disjunct of Next looks like

/\ \E n \in Node : x_arr[n]' = x_arr[n] + 1
/\ UNCHANGED mx

which, as far as I understand, is valid TLA+, but isn't something that is handled correctly by TLC when evaluating a transition relation to generate next states, since it won't know how to assign the full value of x_arr.

Currently, I've sort of worked around this by defining a few "wrapper" actions inside M that explicitly wrap the actions of C with updates like 

x_arr' = [x_arr EXCEPT ![n] = Child(n)!C1_update]

but I find this isn't ideal, since it requires extra maintenance of these wrapper definitions, and kind of breaks the clean separation between M and C, which is what I was hoping to achieve (i.e. that M only knows about the "interface" of C, which consists of its variables/actions). 

I suppose it may also be possible to, for any Child(n) action, define a more general wrapper that explicitly sets all indices x_arr[m], where m # n, as UNCHANGED, and leaves x_arr[n] as unspecified i.e. able to take on any type-correct value. Then, conjoining such a wrapper condition with a Child(n) action would seem to be handled by TLC, since x_arr has been assigned a concrete value. But, this seems potentially inefficient if all type-correct values for x[n] must be generated before being constrained by the Child(n) action that modifies only x[n].

Curious to hear any thoughts on this kind of paradigm and whether there may be better ways to address this.

Stephan Merz

unread,
Jul 27, 2024, 9:49:47 AMJul 27
to tla...@googlegroups.com
Hi Will,

you may want to look at Chapter 10.2 of Specifying Systems that discusses a similar issue. In particular, the formula

/\ \E n \in Node : x_arr[n]' = x_arr[n] + 1
/\ UNCHANGED mx

is indeed a legal TLA+ formula, but it is too weak for use in the next-state relation because it does not express any condition about values of x_arr[m]’ for m # n. In fact, it does not even assert that x_arr’ is a function, or what the domain of that function would be.

You can remedy those problems by conjoining a type-correctness invariant plus requiring that the other components are unchanged and write something like

/\ x_arr' \in [Node -> Nat]
/\ \E n \in Node : 
      /\ x_arr[n]' = x_arr[n] + 1
      /\ \A m \in Node \ {n} : x_arr[m]' = x_arr[m]
/\ UNCHANGED mx

but TLC will complain that it cannot enumerate the set, and if you override Nat to a suitably small set, it will still be inefficient. As suggested in the chapter mentioned above, defining the updates using EXCEPT expressions is certainly your best option.

Stephan

--
You received this message because you are subscribed to the Google Groups "tlaplus" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tlaplus+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/tlaplus/7e9b3fb8-cc30-48ff-822b-ba4f53f919ben%40googlegroups.com.

Hillel Wayne

unread,
Jul 30, 2024, 9:13:13 AMJul 30
to 'Alex Weisberger' via tlaplus
Could you do `\E n \in Node: x_arr' = (n :> x_arr[n+1]) @@ x_arr`?

(Man that was hard to type in a phone) 


--
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted

William Schultz

unread,
Aug 16, 2024, 10:22:42 AMAug 16
to tlaplus
Thanks for the suggestions. My ultimate goal is to be able to define C's actions independently, such that M can use them without knowing the internals of each action in C. This seems hard to do, though, with the current tooling.

I am curious, though, if TLC could be modified to better support this kind of scenario. For example, as stated above, if you give a transition relation like

/\ \E n \in Node : 
      /\ x_arr[n]' = x_arr[n] + 1
      /\ \A m \in Node \ {n} : x_arr[m]' = x_arr[m]
/\ UNCHANGED mx

to TLC, it currently will not be able to handle it, since it doesn't know x_arr is a function or what its domain is. 

But, if you instead wrote something like:

/\ DOMAIN x_arr' = Node
/\ \E n \in Node : 
      /\ x_arr[n]' = x_arr[n] + 1
      /\ \A m \in Node \ {n} : x_arr[m]' = x_arr[m]
/\ UNCHANGED mx

it seems like this might be a well-defined expression that TLC could evaluate. 

There would presumably be some conditions on when such a formula would be handled by TLC, like 

  (1) the domain of the variable is assigned explicitly and 
  (2) every element in the domain is assigned a value (which holds in the above example). 

and (1) occurs before (2) during evaluation. Otherwise, the function's assigned value would be under-constrained and an error would have to be thrown. Just a thought on how TLC could possibly be made more flexible to handle this type of scenario. 

Stephan Merz

unread,
Aug 19, 2024, 4:25:04 AMAug 19
to tla...@googlegroups.com
DOMAIN x_arr' = Node is not enough, even for the TLA+ semantics, not just for TLC. For example, we have no way of knowing what DOMAIN 42 is. You would have to write

/\ x_arr' \in [Node -> Int]
/\ \E n \in Node : 
      /\ x_arr[n]' = x_arr[n] + 1
      /\ \A m \in Node \ {n} : x_arr[m]' = x_arr[m]
/\ UNCHANGED mx

and making TLC support such a formula requires a major change so that it doesn’t try to enumerate all functions first. (Apalache should handle this better.)

Moreover, it is not clear to me why you’d consider the above definition more modular than writing

/\ x_arr' = [x_arr EXCEPT ![n] = @ + 1]
/\ UNCHANGED mx

since you still have to refer to the domain elements other than n.

Stephan

Simon Singh

unread,
Aug 19, 2024, 7:31:33 AMAug 19
to tla...@googlegroups.com
Hi,

The spec by Markus
uses a module "SequencesExt"

How can I get it? Thanks

Best regards
Simon Singh

Markus Kuppe

unread,
Aug 19, 2024, 7:35:09 AMAug 19
to tla...@googlegroups.com
Message has been deleted

William Schultz

unread,
Aug 22, 2024, 8:45:07 AMAug 22
to tlaplus
Stephan,

Yes, based on the thread here (https://github.com/tlaplus/tlaplus/issues/998) it is now clearer to me why DOMAIN x_arr' = Node is not sufficient to constrain x_arr' to be a function even according to pure TLA+ semantics.

I think my point on modularity effectively still stands though. My issue is that I want to use a defined action inside a child module as is, without the need to explicitly extract its postcondition/update expression. I feel like the paradigm of writing something like

/\ x_arr' = [x_arr EXCEPT ![n] = @ + 1]
/\ UNCHANGED mx

still doesn't solve this. That is, if one of the child modules defines an action like

C1 == x' = x + 1

there is no way to extract the relevant update expression (x + 1) you need without some kind of other workaround or some manually added definitions. This is what was motivating my desire to write this as something like:

/\ x_arr' \in [Node -> Int]
/\ \E n \in Node : 
      /\ Child(n)!C1
      /\ \A m \in Node \ {n} : x_arr[m]' = x_arr[m]
/\ UNCHANGED mx
Reply all
Reply to author
Forward
0 new messages