📌 Is Widening Actually Necessary? (a provocative post)
Started by AbstractKitty  |  247 replies  |  8,341 views  |  Last post: 2 hours ago
abstract-interpretation widening coalgebra fixpoints narrowing octagon-domain
Pages: 1 2 3 4 5 ... 25 Showing posts 21–30 of 247
#21 ¶ permalink

Okay okay okay. I have read GaloisCat's partial retraction from last page and MeownadTransformer's Coq thing very carefully, and I want to refine what I was originally claiming rather than walk it back entirely.

GaloisCat wrote (post #17):
I concede that the standard widening-then-narrowing approach cannot be eliminated wholesale. But AbstractKitty's original claim about coalgebras feels like it's gesturing at something real, even if the formulation was wrong.
MeownadTransformer wrote (post #19):
The Coq formalization suggests the correct framing is: widening is necessary for termination in infinite-height lattices, but the coalgebraic perspective lets you ask whether specific widening choices are canonical or arbitrary. That's a different question from "is widening necessary."

Yes! MeownadTransformer nailed it. I was conflating two questions:

  1. Is widening necessary at all? — Almost certainly yes, for any domain of infinite height. GaloisCat is right about this and I was too glib in the OP.
  2. Is the choice of widening operator canonical or arbitrary? — This is the interesting question. In a coalgebraic framing, the "right" widening for a domain should arguably be derivable from its structure, not chosen ad hoc.

My original claim—that "widening is not necessary"—was wrong as stated. What I should have said is that the ad-hoc nature of most widening operator choices indicates we don't yet have a fully satisfying theoretical account of widening. The necessity is pragmatic and proven, but the theory of which widening to use is much murkier.

Refined Claim: Widening is necessary. The canonicity of widening operators, however, is an open question that coalgebraic and categorical perspectives might help illuminate. The fact that different widening operators for the same domain give wildly different precision is a sign that we're missing something.

Thanks MeownadTransformer for finding the right framing. Sorry for being a chaos agent in the OP. (Not entirely sorry. The engagement is good for the forum.)

-- "a category theorist is someone who can't tell the difference between a coffee cup and a donut, and is proud of it"
#22 ¶ permalink

Since we've (somewhat) settled the philosophical question, let me bring some implementation-level perspective that I think is underappreciated: the question of where to apply widening is at least as important as which widening operator to use.

Bourdoncle's Chaotic Iteration Strategies (1993)

Bourdoncle's classic paper addresses precisely this. The naive approach is to apply widening at every program point on every iteration. This is sound but needlessly imprecise—you're destroying information at nodes that don't require it for termination. Bourdoncle's key insight is:

Core Idea: It helps both precision and efficiency to widen at as few nodes as possible. It is safe to apply widening only at the targets of "feedback edges"—edges that go backwards in a topological ordering of the control flow graph.

These feedback-edge targets are essentially the loop headers. In a structured program with no irreducible control flow, loop headers are exactly the right widening points. Bourdoncle formalizes this using weak topological orderings (WTO) of the dependency graph of the system of semantic equations associated with the program.

The strategies Bourdoncle describes minimize the loss of precision due to widening operators by only applying them where the abstract lattice might diverge—i.e., where there are back-edges in the CFG.

Loop Headers vs. All Program Points

The contrast is stark:

  • All-points widening: Apply at every join point on every pass. Terminates quickly but destroys precision everywhere.
  • Loop-header widening (Bourdoncle): Apply only at heads of strongly-connected components in the CFG. Terminates equally reliably but preserves precision at non-loop join points.

For a completely acyclic program, Bourdoncle's approach requires zero widening applications—the standard iteration terminates on its own because there are no infinite ascending chains to cause divergence.

The WTO Construction

Without going into full detail (I've written about this in the wiki, I'll link it): a WTO of a graph G is a sequence that respects topological order while grouping strongly-connected components into "components" with designated head elements. The head of each component is the widening point. This gives you a clean recursive structure:

-- Pseudocode for recursive chaotic iteration with WTO
stabilize(component C, state σ):
  repeat
    for element e in C (in WTO order):
      if e == head(C):
        σ[e] := σ[e]  f(σ)[e]   -- widen at head
      else if e is a subcomponent:
        stabilize(e, σ)            -- recurse
      else:
        σ[e] := f(σ)[e]            -- normal update
  until σ stable

The "recursive strategy" in Bourdoncle stabilizes subcomponents recursively at each outer iteration, whereas the "iterative strategy" does one pass and checks global stability. Both are sound; the recursive one tends to be more precise on nested loops.

Important nuance: Finding the minimal feedback vertex set (minimum widening points) is NP-complete. Bourdoncle's WTO gives a practical polynomial-time approximation that is good enough for almost all real programs.

tl;dr: widening at loop headers is not just a heuristic, it's a principled strategy that Bourdoncle gives precise complexity bounds for. The "which widening" question and the "where to widen" question are orthogonal and both matter.

-- 8,774 posts of accumulated lattice grief
#23 ¶ permalink

Hi everyone!! I'm new here and this thread has been so helpful for understanding abstract interpretation. LatticeTabby's explanation of loop headers makes a lot of sense to me now (I'd seen widening applied everywhere in example code and wondered why).

Sorry if this is a basic question but I keep seeing NarrowingEnjoyer mention "narrowing" in basically every thread on this forum and I have no idea what it is?? From context it seems like the opposite of widening but I'm not sure what that means concretely.

Like, widening is for making sure the ascending chain terminates, right? So does narrowing go down? Why would you want to go down from a fixpoint?

I looked it up and found the Cousot & Cousot papers but they are.... very dense. Is there a cat-friendly explanation? 🐾

Also: is narrowing always safe? It seems like if you go "more precise" after the widening you might accidentally exclude some actual program behaviours?

#24 ¶ permalink

⚠️ Long post warning. I have been waiting for someone to ask this. You have summoned me.

ProgrammingPawsy wrote:
Is there a cat-friendly explanation? 🐾

ProgrammingPawsy, yes. Pull up a chair. This is my moment.

The Setup: Why Widening Overshoots

You're right that widening is for ascending chain termination. But the price of widening is imprecision. Widening deliberately overshoots the true fixpoint to guarantee termination. Consider a simple example:

x = 1;
while(*) {
  x = 2;
}

Iterating the interval domain without widening:

  • Iteration 0: x ∈ [1,1] (at loop entry)
  • Iteration 1: x ∈ [1,2] (join with loop body)
  • Iteration 2: x ∈ [1,2] (stable! ✓)

Great, this terminates fine. But with a loop that increments x:

x = 0;
while(x < 100) { x++; }
  • Iteration 0: x ∈ [0,0]
  • Iteration 1: x ∈ [0,1]
  • Iteration 2: x ∈ [0,2]
  • ...
  • Forever. 😿

So we apply widening . The standard interval widening goes:

[a,b] ∇ [c,d] = [ (c < a ? −∞ : a), (b < d ? +∞ : b) ]

So after a couple of iterations with widening:

  • Iteration 0: x ∈ [0,0]
  • Iteration 1 (widen): [0,0] ∇ [0,1][0, +∞]
  • Iteration 2: stable (post-fixpoint).

But the actual post-loop value is x ∈ [100, 100]! We computed [0, +∞], which is massively imprecise. Widening shot past the target.

Enter Narrowing: The Descending Phase

Narrowing is the answer to "okay we have a safe over-approximation, can we make it more precise without becoming unsound?" It is a descending phase that runs after widening has found a post-fixpoint.

Formally, a narrowing operator must satisfy two conditions:

1. ∀x, y ∈ D̂. (y ⊑ x) ⟹ y ⊑ (x △ y) ⊑ x
2. For all decreasing chains x₀ ⊒ x₁ ⊒ ..., the sequence y₀ = x₀, yᵢ₊₁ = yᵢ △ xᵢ₊₁ converges.

Condition (1) says: narrowing always produces something between the two inputs. If y ⊑ x, then x △ y is below x (we're going down) but above y (we're not going below the true fixpoint). Critically, this guarantees soundness is preserved—you never go below the true least fixpoint.

Condition (2) says: the descending sequence terminates. No infinite descending chains allowed.

The Interval Narrowing Operator

For intervals specifically, the narrowing is beautifully simple:

[a,b] △ [c,d] = [ (a = −∞ ? c : a), (b = +∞ ? d : b) ]

In English: if our widened bound is infinite, replace it with the more precise bound from the current iterate; otherwise keep the widened bound. This restores precision lost only due to widening, without dropping below the true fixpoint.

Back to our example. After widening we have x ∈ [0, +∞] as our post-fixpoint. Now we run the narrowing phase:

  • Apply transfer function to [0, +∞]: we get something like [0, 100] (from the guard x < 100)
  • Narrow: [0,+∞] △ [0,100] = [0,100]
  • Apply transfer again: stable.

Much better! We went from [0,+∞] to [0,100] after just one narrowing step.

Why Is It Safe?

To directly answer ProgrammingPawsy's safety question: yes, narrowing is safe, for the following reason. After widening, we have a post-fixpoint—an abstract value such that F(x̂) ⊑ x̂, where F is the abstract transfer function. This means is already above the least fixpoint (the true analysis result). Narrowing can only descend from , but condition (1) ensures we never go below the least fixpoint. So the narrowed result is still a safe over-approximation of the true semantics.

Intuition: Widening finds a "ceiling." Narrowing carefully lowers the ceiling without ever going below the true answer. The ceiling always stays above reality (soundness). We just want it as tight as possible (precision).
Limitations of Narrowing

Narrowing is wonderful but not magical. Some caveats:

  • Not all domains have good narrowing operators. For the polyhedra domain, narrowing can be tricky to define in a way that terminates efficiently.
  • Narrowing doesn't always reach the true fixpoint. It produces some post-fixpoint below the widened one, but not necessarily the least fixpoint. Precision improvement varies.
  • Narrowing only helps if widening overshot. If your widening was already precise (e.g., you used widening with thresholds), narrowing may do little.
  • Widening + narrowing is not the same as exact fixpoint computation. The composition is still a heuristic, just a well-studied one.

Also: there's a very interesting 2012 paper by Halbwachs and Henry ("When the decreasing sequence fails") about cases where narrowing doesn't give you what you expect. Worth reading if you go deeper into this.

The Grand Picture

So to summarize the widening/narrowing duet:

  1. Ascending phase (widening): Start from , compute iterates with to find a post-fixpoint. Terminates. May be imprecise.
  2. Descending phase (narrowing): Start from post-fixpoint, compute iterates with to improve precision. Terminates. Stays sound.

This is why I keep mentioning narrowing. It's the unsung hero of abstract interpretation. Everyone talks about widening because it's the "hard" part that makes things terminate, but narrowing is what makes the result usable in practice. I feel seen, ProgrammingPawsy. Thank you for asking. 🔴

-- narrowing enjoyer since 2020 | "descend responsibly"
#25 ¶ permalink

NarrowingEnjoyer, that was genuinely one of the best explanations of narrowing I've read on this forum. Archiving it in the wiki when I have time.

I want to bring in a real-world case study that I think crystallizes the entire thread debate: the Astrée static analyzer.

Astrée: Abstract Interpretation in the Wild

Astrée (Analyseur statique de logiciels temps-réel embarqués) is an abstract interpretation-based static analyzer developed at ENS Paris, starting around 2001. Its main achievement:

Famous result: Astrée was applied to the fly-by-wire flight control software of Airbus aircraft and proved the complete absence of runtime errors with zero false alarms—for hundreds of thousands of lines of safety-critical C code.

This is not a toy. This is the real thing. And widening (with narrowing) is absolutely central to how it works.

How Astrée Uses Widening

Astrée's architecture is a hierarchical network of cooperating abstract domains. The basic domains include intervals and octagons (weakly relational), but it also has specialized domains for digital filters, arithmetic-geometric progressions, and decision trees. The key insight is that most of Astrée's abstract domains are infinitary, requiring convergence acceleration via widening and narrowing.

Rather than a single widening operator, Astrée uses collaborative widening: when the interval domain detects a non-stable variable range, it broadcasts to the whole domain network, allowing all domains to cooperate in finding a stable over-approximation. This is much more precise than having each domain widen independently.

Notably, Astrée uses widening with thresholds: instead of immediately jumping to ±∞, the widening first tries a set of "threshold" values (often derived from constants appearing in the program). This dramatically improves precision for typical embedded control code.

Why This Matters for the Thread

To AbstractKitty's refined point: Astrée's success partially vindicates the "widening is necessary but the choice matters enormously" view. The Airbus result required years of domain-specific tuning of widening strategies. A naive widening would produce mountains of false alarms on the same code.

So yes, widening is necessary. But "which widening" and "where to widen" and "how to combine widenings across multiple domains" is genuinely hard and Astrée represents the state of the art in getting it right for a specific class of programs.

-- "Every program has a least fixpoint. Finding it is the hard part."
#26 ¶ permalink

FixpointFeline's Astrée point is good, but I want to push back on something. You mentioned octagons as just one of the standard domains. But the octagon domain has a subtlety with widening that I think gets glossed over in introductory treatments, and it's relevant to the "which widening" question.

The Octagon Domain's Widening Problem

The octagon abstract domain (introduced by Miné) represents constraints of the form ±X ± Y ≤ c. It's weakly relational—more expressive than intervals, much cheaper than full polyhedra. Astrée uses it for exactly this reason.

Now, the octagon domain requires a closure operation (essentially shortest-path closure, like Floyd-Warshall) to maintain a canonical form. The problem: the standard widening for octagons needs to be special-cased relative to the generic widening you'd get from lifting interval widening. If you naively apply interval-style widening constraint-by-constraint to an octagon, you get something that is not guaranteed to be closed—and then your subsequent operations on the "closed" octagon are unsound.

So my claim: the octagon domain requires a domain-specific widening that accounts for its closure invariant. You cannot just use a generic widening and expect things to work.

This seems like evidence for AbstractKitty's original intuition: there's something structurally significant about the choice of widening, specific to the domain. It's not just a pragmatic choice.

#27 ¶ permalink
GaloisCat wrote:
the octagon domain requires a domain-specific widening that accounts for its closure invariant. You cannot just use a generic widening and expect things to work.

GaloisCat, I respectfully disagree with how you're framing this.

The octagon widening in Astrée is indeed applied point-wise to the constraints: given constraints ±X ± Y ≤ c in one octagon and ±X ± Y ≤ d in another, the widening compares them and either keeps c (if stable) or goes to the next threshold. This is not really "special-cased" in the sense of requiring a fundamentally different algorithm—it's the natural lifting of a threshold-based widening to the constraint level.

The closure issue you're describing is real but it's handled by doing closure after the widening step, not by changing the widening algorithm itself. The Astrée paper is explicit about this: the widening is applied without constraint propagation (to avoid breaking convergence), and closure is a separate post-processing step.

Critical distinction: Mixing widening (which loosens bounds) with constraint propagation (which tightens them) can break convergence of the iterates. Astrée specifically uses a "pre-widening" that avoids this by not applying closure during the widen step.

So I'd say: it's not that octagons need a "special" widening, but that you need to be careful about when you apply closure relative to widening. That's an implementation concern, not a fundamental domain-theoretic one. A careful generic implementation handles it correctly.

I might be wrong here—I'd want to look at Miné's octagon paper more carefully. But I don't think your conclusion follows.

#28 ¶ permalink

I've actually read both the Miné octagon paper and the Astrée combination paper, so let me try to mediate.

You are both partially right, in the annoying way where the argument is partly about definitions.

GaloisCat is right that: the naive approach of applying a pointwise interval-style widening to octagon constraints and then applying closure afterwards does not preserve convergence. Miné's paper explicitly warns about this. The convergence proof breaks because closure (shortest-path propagation) is not monotone with respect to the ordering induced by widening.

FixpointFeline is right that: Astrée's solution—using a "pre-widening" that skips the closure step during widening—is correct and does give a sound, converging iteration. In this sense, "you can make it work" is true.

The synthesis: the octagon domain requires a modified widening procedure that differs from what you'd naively construct. Whether you call that "special-casing the widening" (GaloisCat) or "correctly sequencing closure with respect to widening" (FixpointFeline) is somewhat definitional. The mathematical content is the same: you cannot treat the octagon as a black box and plug in an off-the-shelf widening.

LatticeTabby's verdict: The octagon example supports AbstractKitty's point that widening choices are domain-specific and non-trivial. It doesn't support the claim that widening is "unnecessary," but it does support the claim that the theory of domain-canonical widening is underdeveloped.

This is exactly the kind of thing a good categorical account of widening operators might help clarify—if only such an account existed in full generality. MeownadTransformer, is this something the Coq formalization touches on?

#29 ¶ permalink
LatticeTabby wrote:
The synthesis: the octagon domain requires a modified widening procedure that differs from what you'd naively construct. Whether you call that "special-casing" or "correctly sequencing" is somewhat definitional.

...Okay, fine. I accept the mediation. LatticeTabby as always playing the neutral arbiter with 8000+ posts of accumulated patience.

FixpointFeline: sorry for the sharp tone. I think I was overconfident. The distinction between "special-cased widening" and "widening + carefully ordered closure" is a real one and I collapsed it.

I'll look at the actual Miné octagon paper this weekend. I've been operating from memory and clearly my mental model of the closure/widening interaction is off.

One question for LatticeTabby: does the convergence proof break for all closure-based domains (e.g., zones/DBMs), or is this specific to the octagon because of some property of the octagon closure? I assumed it was a general issue with "domains that need normal forms" but maybe it's more specific.

#30 ¶ permalink

It's 4am and I'm watching GaloisCat and FixpointFeline argue about closure ordering so I think this thread is going well.

Let me try to write a thread summary for ProgrammingPawsy and any other new readers, since we're now 30 posts in and the landscape has shifted significantly from the OP.

🧵 Thread Summary: Posts 1–30
  1. Original claim (me, OP): "Widening is not necessary if you have a suitable coalgebraic structure." — This was wrong as stated. Retracted.
  2. GaloisCat's correction: Widening is necessary for termination on infinite-height lattices. Period. The Cousots proved this 40+ years ago.
  3. MeownadTransformer's framing: The interesting question is canonicity of widening operators, not necessity. Coalgebras might illuminate which widening is "right" for a given domain, but they don't eliminate the need for widening.
  4. My refined claim: Widening is necessary. The theory of which widening to use is underdeveloped. The ad-hoc nature of most widening choices is a theoretical gap worth filling.
  5. LatticeTabby on Bourdoncle: Where you apply widening matters. Bourdoncle's WTO framework shows you should apply widening only at loop headers (heads of SCCs), minimizing precision loss.
  6. NarrowingEnjoyer's explanation: Narrowing is a descending post-processing phase that recovers precision lost to widening while preserving soundness. It's essential in practice.
  7. FixpointFeline on Astrée: Real-world vindicaton. Astrée proved absence of runtime errors in Airbus flight control software using abstract interpretation with carefully tuned widening + narrowing.
  8. GaloisCat vs FixpointFeline: Whether the octagon domain needs "special" widening — ongoing. LatticeTabby's synthesis: the convergence issue with octagon closure is real and supports the "widening is domain-specific" view.
OP Verdict: I was wrong about widening being unnecessary. I was not wrong that the theoretical account of widening is incomplete. The thread has been extremely educational. Thank you all, especially NarrowingEnjoyer for the best narrowing explanation I've ever read, and LatticeTabby for keeping everyone honest.

Open questions for the rest of the thread:

  • GaloisCat's question: does the closure/widening issue generalize to all DBM-based domains (zones etc.) or just octagons?
  • LatticeTabby's question to MeownadTransformer: does the Coq formalization address canonicity of widening at all?
  • My follow-up question: is there any categorical account of widening operators that gives a "canonical" choice for standard abstract domains?

page 4 incoming, I assume 😼

-- chaos agent and occasional OP
Pages: 1 2 3 4 › 5 ... 25 »
💬 Quick Reply
🔔 Subscribe
📋 Related Threads
🔴 "Narrowing actually works" — a follow-up to this thread by NarrowingEnjoyer 📐 Weak Topological Orderings: a comprehensive guide (LatticeTabby's magnum opus) 🐾 Astrée deep dive: how it actually analyzed the Airbus code 😼 Octagon domain closure and widening — the definitive argument λ MeownadTransformer's Coq formalization of abstract interpretation 📖 CGPA Reading Group: Cousot & Cousot POPL '77 🔬 The Zoo of Polyhedral Widening Operators