Skip to content

Growth domain: X*X and X^2 give different normal forms (non-minimal, safe) #1088

Description

@isPANN

Background

The growth domain (src/growth.rs) computes a Big-O normal form as an antichain of monomials. Two operators that are mathematically identical — X * X (Expr::Mul) and X^2 (Expr::Pow) — produce different normal forms when X is a multi-term antichain:

$ pred-sym big-o "(n + m)^2"
O(m^2 + n^2)
$ pred-sym big-o "(n + m)*(n + m)"
O(m*n + m^2 + n^2)
$ pred-sym compare "(n+m)*(n+m)" "(n+m)^2"
Big-O equal: false

Root cause: Pow(X, 2) uses the raise-each-term rule (a deliberate widening introduced so sqrt((n-m)^2) ≍ n+m), which yields {n², m²}. Mul(X, X) uses the general antichain product, which yields all cross terms {n², n·m, m²}. The cross term n·m is asymptotically dominated by the sum n² + m² (AM–GM: n·m ≤ (n²+m²)/2), but the antichain order is per-term — no single term dominates n·m — so it is kept.

Why this is not a soundness bug (but worth fixing)

Both results are valid upper bounds — Mul merely over-approximates, Pow gives the tighter (and also valid) answer. Dominance decisions stay safe (an over-approximation never under-states growth), so find_dominated_rules and the measured search's pre-flight guard remain sound. The observable costs are:

  1. pred-sym compare reports "not equal" for two expressions that are the same growth class, whenever one is written with * and the other with ^.
  2. Composed overheads that happen to multiply an antichain by itself render a non-minimal Big-O (extra cross terms) in pred path.

Objective

Make X * X and X^2 produce the same normal form — either by having Mul recognize a self-product, or (more generally) by teaching the antichain to drop a term that is dominated by the sum of other terms (AM–GM pruning), not just by a single term. Decide which; the second is more general but changes the antichain invariant, so it needs care.

Verification

pred-sym compare "(n+m)*(n+m)" "(n+m)^2"equal, and pred-sym big-o "(n+m)*(n+m)"O(m^2 + n^2) (no m*n term). Add a growth unit test pinning Mul(X,X) == Pow(X,2) for a multi-term X.

Negative control: the same test on today's code fails (the two forms differ) — proving the fix changed real behavior.

Related

Found during the old-vs-new correctness audit of PR #1083 (growth domain). Independent of the milestone's shipped correctness — this is a tightness/consistency refinement, not a soundness fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions