Skip to content

DieHardest: compare jug configurations via parallel and interleaved self-composition#199

Merged
lemmy merged 2 commits intomasterfrom
mku-DieHardest
Mar 2, 2026
Merged

DieHardest: compare jug configurations via parallel and interleaved self-composition#199
lemmy merged 2 commits intomasterfrom
mku-DieHardest

Conversation

@lemmy
Copy link
Member

@lemmy lemmy commented Feb 13, 2026

Add DieHardest.tla, which composes two instances of DieHarder to compare which configuration has the shorter path to the Goal (state).

Four composition approaches are contrasted in clearly labeled sections that progressively motivate interleaved composition:

  1. Parallel — shows that BFS finds the shortest path for the slower configuration but not the faster one.
  2. Parallel + "per-behavior freeze" — shows that freezing after reaching the Goal does not help.
  3. Parallel + "global BFS-level freeze" — correct, but relies on TLC-specific operators.
  4. Interleaved — each instance steps independently, so BFS minimizes both step counts.

@lemmy lemmy self-assigned this Feb 13, 2026
@lemmy lemmy force-pushed the mku-DieHardest branch 2 times, most recently from d872bd6 to f9d4e0c Compare February 25, 2026 18:49
@lemmy lemmy changed the title DieHardest: compare jug configurations via self-composition DieHardest: compare jug configurations via parallel and interleaved self-composition Feb 25, 2026
@lemmy lemmy requested a review from Copilot February 25, 2026 19:45

This comment was marked as resolved.

@lemmy lemmy marked this pull request as ready for review February 25, 2026 22:13
@lemmy
Copy link
Member Author

lemmy commented Feb 25, 2026

@hwayne Could you please review this example? It references your “Hypermodeling Hyperproperties” article.

(* *)
(* Both paths are individually shortest. However, TLCSet and TLCGet *)
(* are TLC-specific operators outside the logic of TLA+, making this *)
(* approach incompatible with other TLA+ tools (e.g. Apalache). *)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, unless things changed recently, TLC global registers are local to each worker

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this would also only work with a single worker.

@hwayne
Copy link

hwayne commented Feb 26, 2026

These solutions depend on two assumptions: that the states are searched with BFS, and that the BFS is strict. The first is violated with the -depth and -simulate parameters. I've seen the second be violated when using multiple workers:

  1. worker 1 picks the last state of length N on the queue
  2. worker 2 picks the first state of length N+1
  3. Both workers are evaluating a state that violates an invariant, but
  4. worker 2 reaches the violation before worker 1 does.

It's pretty rare, but I've seen it happen!

A robust and universal TLA+ spec would probably have to store the diehard traces as data values and implement Dijkstra's algorithm to find the shortest path, with the property that <>[](shortest_c1 <= shortest_c2).

@lemmy
Copy link
Member Author

lemmy commented Feb 26, 2026

These solutions depend on two assumptions: that the states are searched with BFS, and that the BFS is strict. The first is violated with the -depth and -simulate parameters. I've seen the second be violated when using multiple workers:

  1. worker 1 picks the last state of length N on the queue
  2. worker 2 picks the first state of length N+1
  3. Both workers are evaluating a state that violates an invariant, but
  4. worker 2 reaches the violation before worker 1 does.

It's pretty rare, but I've seen it happen!

A robust and universal TLA+ spec would probably have to store the diehard traces as data values and implement Dijkstra's algorithm to find the shortest path, with the property that <>[](shortest_c1 <= shortest_c2).

Strict BFS has in fact been an implicit assumption in the current formulation. This directly contradicts the spec's own statement: "This approach stays entirely within the logic of TLA+ and is compatible with all TLA+ tools."

Given that contradiction, what do you think about removing that statement and instead making the assumption explicit, for example:

ASSUME TLCGet("config").mode = "bfs" /\ TLCGet("config").worker = 1

In my view, the example would still offer an interesting and valuable perspective on self-composition, even with this assumption made explicit.

@lemmy lemmy force-pushed the mku-DieHardest branch 2 times, most recently from ec376c8 to 64aa8a8 Compare February 26, 2026 23:27
@lemmy lemmy requested a review from hwayne February 27, 2026 15:58
@hwayne
Copy link

hwayne commented Feb 27, 2026

These solutions depend on two assumptions: that the states are searched with BFS, and that the BFS is strict. The first is violated with the -depth and -simulate parameters. I've seen the second be violated when using multiple workers:

  1. worker 1 picks the last state of length N on the queue
  2. worker 2 picks the first state of length N+1
  3. Both workers are evaluating a state that violates an invariant, but
  4. worker 2 reaches the violation before worker 1 does.

It's pretty rare, but I've seen it happen!
A robust and universal TLA+ spec would probably have to store the diehard traces as data values and implement Dijkstra's algorithm to find the shortest path, with the property that <>[](shortest_c1 <= shortest_c2).

Strict BFS has in fact been an implicit assumption in the current formulation. This directly contradicts the spec's own statement: "This approach stays entirely within the logic of TLA+ and is compatible with all TLA+ tools."

Given that contradiction, what do you think about removing that statement and instead making the assumption explicit, for example:

ASSUME TLCGet("config").mode = "bfs" /\ TLCGet("config").worker = 1

In my view, the example would still offer an interesting and valuable perspective on self-composition, even with this assumption made explicit.

I think this make sense, but then section 3's main downside (only TLC) then applies to all of the specs.

Add DieHardest.tla, which composes two instances of DieHarder to
check a 2-hyperproperty as an ordinary invariant: given two jug
configurations that can each solve the Die Hard problem, which one
reaches the Goal in fewer steps?  The invariant NotSolved holds as
long as both configurations have not yet reached the Goal; a
counterexample is a behavior in which both solve the problem,
revealing which configuration is faster.

The module also defines HasSolution, a GCD-based predicate (via
Bézout's identity) used in ASSUME to reject unsolvable
configurations before model checking begins.

Four composition approaches are contrasted in clearly labeled
sections that progressively motivate interleaved composition:

  1. Parallel — shows that BFS finds the shortest path for the slower
     configuration but not the faster one.
  2. Parallel + per-behavior freeze — shows that freezing after
     reaching the Goal does not help.
  3. Parallel + g BFS-level freeze — correct, but relies on
     TLC-specific operators outsi the logic of TLA+.
  4. Interleaved — the clean solution: each instanceteps
     independently, so BFS minimizes both step counts.

Annotated counterexample traces in Sections 1 and 3 illustrate the
difference concretely.

MCDieHardest instantiates the spec with Goal = 4, comparing a 2-jug
<<5, 3>> setup against a 3-jug <<5, 3, 3>> setup — the duplicate jug
reduces the shortest solution from 6 to steps.

Co-authored-by: Dmitry Kulagin <craft095@users.noreply.github.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>

Remove redundant TLC ASSUME BFS and Workers = 1

Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
@lemmy lemmy force-pushed the mku-DieHardest branch 2 times, most recently from b6be971 to a0875fc Compare March 2, 2026 16:57
Add APADieHardest.tla, an Apalache-compatible configuration that checks
DieHardest's interleaved composition (Section 4) with --cinit for
constant initialization and [s \in S |-> ...] in place of :> and @@.
Note the Apalache incompatibility of NextParallelGlobalFreeze in
DieHardest.tla (TLCGet takes both Str and Int arguments).

Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
@lemmy
Copy link
Member Author

lemmy commented Mar 2, 2026

Added an Apalache module that, although slightly slower, produces the same counterexample for NextInterleaved as TLC. This confirms that the NextInterleaved approach is not specific to TLC.

That said, it is worth noting that TLA itself does not guarantee that a shortest counterexample will be found. Identifying shortest counterexamples is an implementation detail of particular verification tools, such as TLC and Apalache, rather than a property of the TLA language.

@lemmy
Copy link
Member Author

lemmy commented Mar 2, 2026

@ahelwer This PR enhances the GitHub automation to support overriding the -workers parameter.

@ahelwer
Copy link
Collaborator

ahelwer commented Mar 2, 2026

Nice! An alternative way to do it which is less directly bound to TLC would be to have an optional boolean parameter to the model in the manifest.json that is called something like parallelizeStateExploration. If this parameter is missing it defaults to true. Then TLC is run with either all the threads available on the machine or just in one thread. It is unlikely that someone would want to specifically run TLC in only two threads, for example. But this way you've done it is fine.

@lemmy lemmy merged commit 9ac1cdc into master Mar 2, 2026
7 checks passed
@lemmy lemmy deleted the mku-DieHardest branch March 2, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants