Skip to content

observer: entropy is a function of |x| alone with level sets {±x, ±1/x}, so H(x) ≡ H(1/x) — the entropy channel cannot see sign-flip or reciprocal oscillation #862

Description

@InauguralPhysicist

Finding

entropy_of_num (src/eigenscript.c:257) is H₂(1/(1+|x|)). Two identities follow from the formula alone:

  • H₂(p) = H₂(1−p), and p(1/x) = 1/(1+1/x) = x/(1+x) = 1 − p(x), so H(x) ≡ H(1/x) exactly.
  • H reads fabs(num), so H(x) ≡ H(−x).

The level set of every value is therefore {x, −x, 1/x, −1/x}. Any trajectory confined to that set has dH = 0 at every step and is invisible to all six predicates and to report.

Numerically confirmed at v0.38.0 / 078e759:

H(2)     = 0.9182958340544896      H(0.5)   = 0.9182958340544896
H(1000)  = 0.011397802630112312    H(0.001) = 0.011397802630110919
H(7)     = 0.5435644431995964      H(-7)    = 0.5435644431995964

H(1000) and H(0.001) agree to ~1e-15 — three orders of magnitude below dh_zero = 0.001.

Reproduction

define a as:
    x is 5.0
    for i in range of 20:
        x is 0 - x               # ±5 forever: the simplest possible oscillator
    print of ("sign-flip: report=" + (report of x) + " oscillating=" + (str of (oscillating of x)))
    return 0
a of 0

define b as:
    y is 1000.0
    for i in range of 20:
        y is 1.0 / y             # 1000 <-> 0.001, ratio 1e6
    print of ("recip:     report=" + (report of y) + " converged=" + (str of (converged of y)))
    return 0
b of 0
sign-flip: report=equilibrium oscillating=0
recip:     report=converged converged=1

The second case is the worse one: a sequence swinging over six orders of magnitude fires the strongest band.

Distinct from #422

#422 covers the value channel's sub-deadband oscillation blindness (a flip below dh_zero is not counted). This is the entropy channel, and the mechanism is different: the steps here are enormous, not sub-deadband — they are simply invisible to a magnitude-only, non-injective measure. report_value classifies both cases correctly as oscillating, which is the point: the channel that is wrong is the one every bare predicate, report, and loop while not converged read by default.

Why it matters

README.md:150-157 advertises oscillation detection with a 5.0 / 2.0 alternation. That example works only because |5| ≠ |2| and 5 ≠ 1/2. Move it onto a level set and detection silently inverts. docs/PREDICATES.md:479 already documents the non-monotonicity (entropy peaks at |x| = 1); the non-injectivity is undocumented and has sharper consequences.

Options

  1. Make the value channel the default for report and the bare predicates, demoting entropy to an opt-in diagnostic. Note this trades these failures for Observer value channel (report_value) is blind to sub-exponential divergence and sub-deadband oscillation (relative-delta normalization) #422's and observer: report_value classifies monotone CONVERGENCE as 'diverging' (rate > ~0.87) #674's, so it needs the value channel's floor raised first — not a free swap.
  2. Keep entropy as the default but break the x → 1/x symmetry (e.g. sign-aware or signed-log measure).
  3. Document the level sets in PREDICATES.md next to the existing |x| = 1 note, and have report cross-check the value channel before returning a rest band.

Related: #422, #294, #412, #735.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions