The Predicate Transformer wp

Dijkstra’s weakest-precondition calculus, as Gries teaches it — a single operator that turns a goal into the exact requirement on the starting state, and with it captures total correctness in one line of algebra.

Every proof in The Science of Programming rests on one operator: wp, the weakest precondition predicate transformer, borrowed from Dijkstra’s A Discipline of Programming. It is the mathematical engine behind “develop the program and its proof together.” Given what you want to be true when a command finishes, wp tells you the most permissive thing that must be true before it starts — and it does so by working backward from the goal, which is exactly the direction in which you design. This page defines wp, connects it to Hoare triples, states its healthiness laws, and works a small proof by pushing a postcondition backward through a program.

This is an original study summary for quick reference. Notation follows Gries and Dijkstra: wp(S, R) for the weakest precondition of command S with respect to postcondition R; {P} S {Q} for a Hoare triple; for implication; T/F for the universally true/false predicates. See the book for the full, rigorous treatment.

Contents

  1. Definition
  2. Hoare triples and how they relate
  3. The predicate transformer view
  4. Healthiness / basic properties
  5. Using wp to prove a triple
  6. Weakening and strengthening
  7. Summary

Definition

Fix a command S and a desired postcondition R. The weakest precondition is defined as a set of starting states:

wp(S, R) = the set of all states such that execution of S
begun in such a state is GUARANTEED to terminate
in a state satisfying R.

Read it in one breath: “the weakest precondition under which S is guaranteed to establish R.” Two words carry all the weight.

Because it is a set of states, wp(S, R) is itself a predicate. For a deterministic terminating command S, membership is decidable in principle by running S; the value of the calculus is that you compute the predicate symbolically, without running anything.

Hoare triples and how they relate

The older and more familiar notation is the Hoare triple. The triple {P} S {Q} is an assertion about S:

If P holds in the starting state and S is executed, then Q holds in the resulting state.

There is a fork in what that sentence means, and it matters:

The bridge between the triple and the transformer is a single equivalence — for total correctness:

{P} S {Q}  ⇔  P ⇒ wp(S, Q)

Unpack it. wp(S, Q) is the set of all states from which S is guaranteed to reach Q. The triple {P} S {Q} holds exactly when every state satisfying P is one of those good states — i.e. when P is contained in wp(S, Q), which as predicates is written P ⇒ wp(S, Q).

The immediate consequence: wp(S, Q) is the weakest valid precondition for establishing Q. Any P that makes the triple true must imply it, and wp(S, Q) itself is the largest P that works (it trivially implies itself). Every other correct precondition is a strengthening of it. This is precisely why deriving wp backward from the postcondition is the engine of program construction: it hands you the exact obligation, not an approximation, at every step. The assignment axiom and the loop rules that build on this appear in the commands and their wp and the loop and its invariant.

The predicate transformer view

The name predicate transformer is literal. Each command S is read not as a state-to-state action but as a function on predicates: it maps a postcondition to a precondition.

wp(S, ·) : Predicate → Predicate
  R  ↦  wp(S, R)

The direction is the whole point. The machine runs forward — from an input state, through S, to an output state. Reasoning with wp runs backward — from the goal R you want at the end, back to the requirement at the start. You begin with the answer and derive the question. For sequential composition this backward flow is mechanical: wp(S1 ; S2, R) = wp(S1, wp(S2, R)) — you transform R through S2 first, then push the result through S1, right-to-left, opposite to execution order.

Contrast: the strongest postcondition sp

There is a dual transformer that runs the other way. The strongest postcondition sp(S, P) is the strongest (smallest, most informative) predicate Q such that {P} S {Q} holds — it takes a precondition and pushes it forward to describe exactly the set of states S can produce from P. The two are linked through the triple: {P} S {Q} holds iff P ⇒ wp(S, Q) and (for partial correctness) iff sp(S, P) ⇒ Q.

Both are valid semantics, but Gries deliberately builds the discipline on wp, not sp. The reason is directional: program development starts from a known goal (the postcondition, the specification) and searches for code and preconditions that reach it. A backward transformer matches that search — you always have the target in hand and ask what must precede it. Forward reasoning with sp is natural for analyzing an existing program (given the input, what comes out?) but awkward for synthesizing one, because you would be computing outputs while still hunting for the code that should produce them. For deriving programs from specifications, wp is the right tool.

The healthiness / basic properties

Dijkstra observed that anything deserving to be called a command must have a wp obeying a handful of laws — the healthiness properties. They are simultaneously a sanity check on candidate semantics and a working toolkit for proofs.

LawStatementMeaning & why it holds
Law of the Excluded Miraclewp(S, F) = FF is the impossible postcondition — the empty set of states. No command can finish in a state that satisfies F, because there are none. So no starting state can guarantee it, and the precondition is empty too. A command that could establish F would be a “miracle”; the law forbids miracles.
Distributivity of conjunctionwp(S, Q) ∧ wp(S, R) = wp(S, Q ∧ R)Guaranteeing you can reach Q and, separately, guaranteeing you can reach R is the same as guaranteeing you reach both at once. It holds because S has one behavior from a given state: if that single outcome satisfies Q and satisfies R, it satisfies Q ∧ R. This equality is what lets you split a compound goal and prove each conjunct independently.
Monotonicityif Q ⇒ R then wp(S, Q) ⇒ wp(S, R)A weaker goal has a weaker (or equal) requirement. If reaching Q already implies reaching the laxer R, then any state guaranteeing Q also guarantees R, so wp(S,Q) sits inside wp(S,R). Intuitively: asking for less can never demand more. (This follows from ∧-distributivity, since Q ⇒ R means Q ∧ R = Q.)
Distributivity of disjunctionwp(S,Q) ∨ wp(S,R) ⇒ wp(S, Q∨R) — equality iff S is deterministicIf a state guarantees Q, or guarantees R, it certainly guarantees Q∨R, so the implication always holds. The converse can fail under nondeterminism: a state might guarantee Q∨R (every run lands in one or the other) yet guarantee neither individually, because different runs choose differently. For a deterministic S there is only one run, so “reaches Q∨R” forces “reaches Q” or “reaches R,” and equality returns. Nondeterminism strictly weakens the left side.

A useful corollary of the first two: because wp distributes over and respects implication, computing the wp of a conjunctive postcondition is just conjoining the wp of each part — the everyday move when a specification is a bundle of clauses.

Using wp to prove a triple

Here is the calculus in action on the classic three-assignment swap. We want to prove the total-correctness triple

{x = X ∧ y = Y}  t := x ; x := y ; y := t  {x = Y ∧ y = X}

where X and Y are fixed logical constants naming the initial values. By the Hoare–wp bridge, proving the triple means proving the precondition implies wp of the whole body with respect to the postcondition. We compute that wp by pushing the postcondition right-to-left through the three assignments, using the assignment rule wp(“v := e”, R) = R[v := e] (substitute e for v in R) at each step.

# Goal R:  x = Y  and  y = X
# Push R backward through   t := x ; x := y ; y := t

# Step 1 — through the LAST statement  y := t
wp("y := t", x = Y ∧ y = X)
    = (x = Y ∧ y = X)[y := t]        # replace y by t
    = (x = Y ∧ t = X)

# Step 2 — through the MIDDLE statement  x := y
wp("x := y", x = Y ∧ t = X)
    = (x = Y ∧ t = X)[x := y]        # replace x by y
    = (y = Y ∧ t = X)

# Step 3 — through the FIRST statement  t := x
wp("t := x", y = Y ∧ t = X)
    = (y = Y ∧ t = X)[t := x]        # replace t by x
    = (y = Y ∧ x = X)

# Result:  wp(whole body, R)  =  x = X and y = Y

The computed weakest precondition is x = X ∧ y = Y — identical to the stated precondition. So the proof obligation P ⇒ wp(S, Q) becomes (x = X ∧ y = Y) ⇒ (x = X ∧ y = Y), which is trivially true. The triple holds, and the swap is proven correct — no test cases, no tracing execution, just substitution. (Assignment’s domain condition and the full command rules are detailed in the commands and their wp.)

Weakening and strengthening

A valid triple stays valid if you make its precondition harder to satisfy or its postcondition easier to satisfy. This is the rule of consequence, and it is what lets you connect a computed wp to the specification you were actually handed.

P' ⇒ P,  {P} S {Q},  Q ⇒ Q'
——————————————————
∴  {P'} S {Q'}

Two independent moves, either or both:

MoveRuleWhy it is safe
Strengthen the preconditionreplace P by any P' with P' ⇒ PA stronger precondition describes a smaller set of starting states — a subset of those already known good. If S works from every P-state, it works from every P'-state too. You may always ask for more up front.
Weaken the postconditionreplace Q by any Q' with Q ⇒ Q'A weaker postcondition is a larger, laxer target. If every run lands in Q, it certainly lands in the more permissive Q'. You may always promise less at the end. (This is exactly monotonicity of wp viewed through the triple.)

The tie-back to wp: since wp(S, Q) is the weakest precondition, any correct P for a given Q is a strengthening of it — the consequence rule’s left half is just the statement P ⇒ wp(S, Q) in disguise. In practice you compute wp(S, Q), then discharge the gap between your given precondition and it with a single implication, exactly as the swap proof did. This same weakening move is what lets a loop invariant plus a false guard imply the postcondition — see the loop and its invariant.

Summary

IdeaThe one-line takeaway
Definition of wp(S, R)The set of all states from which S is guaranteed to terminate in R — total correctness in one operator.
“Weakest”The most permissive (largest) precondition that still works — exact, not conservative.
Hoare triple{P} S {Q}: if P and S runs, then Q after — partial (if it terminates) vs. total (it does).
The bridge{P} S {Q} ⇔ P ⇒ wp(S, Q); so wp(S,Q) is the weakest valid precondition.
Predicate transformerS is a function from postconditions to preconditions; wp works backward from the goal.
vs. spsp(S,P) pushes forward (strongest post); Gries prefers backward wp for program derivation.
Excluded miraclewp(S, F) = F — nothing can reach the impossible.
∧-distributivitywp(S,Q) ∧ wp(S,R) = wp(S, Q∧R) — split compound goals freely.
MonotonicityQ ⇒ R gives wp(S,Q) ⇒ wp(S,R) — a weaker goal has a weaker requirement.
∨-distributivitywp(S,Q) ∨ wp(S,R) ⇒ wp(S,Q∨R), equality iff deterministic — nondeterminism weakens it.
Proving a triplePush the postcondition right-to-left through the statements; check P ⇒ the result.
Consequence ruleStrengthen the precondition, weaken the postcondition — validity is preserved.
The recurring theme: wp turns correctness into algebra. Once a command’s weakest precondition is known, every question about it — does this triple hold? what must be true before? — reduces to computing a predicate and checking one implication. Reasoning backward from the goal is not just a proof technique; it is the shape of program design itself.