English

r(d)-Kurvenform

Die Flaeche unter der r(d)-Kurve und die Steigung am Wendepunkt tragen unabhaengige Haerteinformation — ueber d(50%) hinaus.

August 2026 · 800 Instanzen (n=40..100) · 4 Merkmale der Sigmoid-Kurve

Kurvenmerkmale

Die r(d)-Kurve (Anteil der d-Literal-Mengen, deren Propagation widerspricht) ist eine Sigmoid-Funktion mit drei Parametern:

d(50%)  = Wendepunkt   (wo r(d) = 0.5)
Steigung = Anstieg bei d(50%)
Frueh    = r(d) bei kleinstem d > 0 (Instabilitaet)
Flaeche = ∫ r(d) dd  (Integral ueber den Messbereich)

Korrelation mit log2(Konflikte)

nd(50%)Flaecher_fruehSteigung (partial)Bestes Einzel-d
40+0.32-0.26-0.14+0.31d=4: -0.43
60+0.25-0.27-0.35+0.10d=4: -0.35
80+0.52-0.52+0.09-0.17d=12: -0.52
100+0.42-0.55-0.36+0.31d=8: -0.57
Pearson r vs. log2(CDCL-Konflikte), nur UNSAT-Instanzen. "Steigung (partial)" = Korrelation der Steigung mit dem Residuum nach Entfernen des d(50%)-Effekts.
Befund. Die Flaeche unter der r(d)-Kurve ist der beste einzelne Haerte-Praediktor bei n=100 (|r|=0.55), gleichauf mit d(50%). Die Steigung am Wendepunkt traegt unabhaengige Information (r_partial = +0.31 bei n=100 und n=40). Das fruehe r(d) (d=4) ist bei n=60 der beste Praediktor (|r|=0.35).
Code & Daten ansehen — die vier Kurvenmerkmale
kurvenform_messung.py (baut auf d50_messung.py auf)
"""Die Form der r(d)-Kurve traegt mehr Information als der einzelne
Kreuzungspunkt d(50%). Der Beleg hinter ordnung_kurvenform.html.

Baut auf d50_messung.py auf (dieselbe Instanzerzeugung, dieselbe
Propagationsmessung r(d)), sampelt aber die ganze Kurve statt nur den
50%-Punkt zu suchen, und zieht vier Merkmale daraus:

  d(50%)    Wendepunkt (wo r(d) = 0.5), wie zuvor
  Flaeche   Integral von r(d) ueber den gemessenen Bereich
  Fruehe    r(d) beim kleinsten gemessenen d (Instabilitaet)
  Steigung  numerische Ableitung von r(d) am Wendepunkt

Aufruf: python3 kurvenform_messung.py [n-liste=40,60,80,100] [instanzen=200] [seed=7]
Schreibt daten_kurvenform.json.
"""
import json
import sys

import numpy as np

from d50_messung import instanzen, r_von_d


def kurve(klauseln, n, dgrid, proben, rng):
    return np.array([r_von_d(klauseln, n, d, proben, rng) for d in dgrid])


def merkmale(dgrid, rd):
    # Flaeche: Trapezregel
    flaeche = float(np.trapezoid(rd, dgrid)) if hasattr(np, "trapezoid") else float(np.trapz(rd, dgrid))
    fruehe = float(rd[0])
    # d(50%) durch das gemessene Gitter interpolieren
    if rd[-1] < 0.5:
        d50 = float(dgrid[-1])
    elif rd[0] > 0.5:
        d50 = float(dgrid[0])
    else:
        i = int(np.searchsorted(rd, 0.5))
        d_lo, d_hi = dgrid[i - 1], dgrid[i]
        r_lo, r_hi = rd[i - 1], rd[i]
        frac = (0.5 - r_lo) / (r_hi - r_lo) if r_hi != r_lo else 0.0
        d50 = float(d_lo + frac * (d_hi - d_lo))
    # Steigung: zentrale Differenz an der Stelle, die d(50%) am naechsten liegt
    i = int(np.argmin(np.abs(dgrid - d50)))
    i = min(max(i, 1), len(dgrid) - 2)
    steigung = float((rd[i + 1] - rd[i - 1]) / (dgrid[i + 1] - dgrid[i - 1]))
    return d50, flaeche, fruehe, steigung


def partielle_korrelation(x, y, kontrolle):
    """Rest von x und y nach linearer Regression auf `kontrolle`, dann Pearson."""
    A = np.column_stack([np.ones(len(kontrolle)), kontrolle])
    bx, *_ = np.linalg.lstsq(A, x, rcond=None)
    by, *_ = np.linalg.lstsq(A, y, rcond=None)
    rx = x - A @ bx
    ry = y - A @ by
    if rx.std() < 1e-12 or ry.std() < 1e-12:
        return 0.0
    return float(np.corrcoef(rx, ry)[0, 1])


def lauf(ns=(40, 60, 80, 100), anzahl=200, seed=7, proben=150):
    ergebnis = {}
    for n in ns:
        R = instanzen(n, anzahl, seed + n)
        unsat = [r for r in R if r["sat"] == 0]
        dgrid = np.unique(np.linspace(2, min(n, 20), 10).round().astype(int))
        rng = np.random.default_rng(3000 + n)

        d50s, flaechen, fruehe, steigungen, konflikte = [], [], [], [], []
        for r in unsat:
            rd = kurve(r["klauseln"], n, dgrid, proben, rng)
            d50, fl, fr, st = merkmale(dgrid, rd)
            d50s.append(d50); flaechen.append(fl); fruehe.append(fr); steigungen.append(st)
            konflikte.append(np.log2(max(r["konflikte"], 1)))
        d50s, flaechen, fruehe, steigungen, y = map(np.array, (d50s, flaechen, fruehe, steigungen, konflikte))

        def r(x):
            return float(np.corrcoef(x, y)[0, 1]) if x.std() > 1e-12 else 0.0

        r_d50, r_fl, r_fr, r_st = r(d50s), r(flaechen), r(fruehe), r(steigungen)
        r_st_partial = partielle_korrelation(steigungen, y, d50s)

        # bestes einzelnes d (Rasterpunkt) als Praediktor
        rng2 = np.random.default_rng(4000 + n)
        beste_d, beste_r = None, 0.0
        for j, d in enumerate(dgrid):
            rds = np.array([r_von_d(rr["klauseln"], n, int(d), 80, rng2) for rr in unsat])
            if rds.std() < 1e-9:
                continue
            rr_ = float(np.corrcoef(rds, y)[0, 1])
            if abs(rr_) > abs(beste_r):
                beste_r, beste_d = rr_, int(d)

        ergebnis[n] = {
            "n": n, "unsat_instanzen": len(unsat), "dgrid": [int(x) for x in dgrid],
            "r_d50": r_d50, "r_flaeche": r_fl, "r_fruehe": r_fr,
            "r_steigung": r_st, "r_steigung_partial": r_st_partial,
            "bestes_d": beste_d, "bestes_d_r": beste_r,
        }
        print(f"n={n:3d}  UNSAT={len(unsat):3d}  d(50%) r={r_d50:+.2f}  Flaeche r={r_fl:+.2f}  "
              f"Fruehe r={r_fr:+.2f}  Steigung r={r_st:+.2f} (partial {r_st_partial:+.2f})  "
              f"bestes d={beste_d} (r={beste_r:+.2f})")

    with open("daten_kurvenform.json", "w") as f:
        json.dump(ergebnis, f, indent=1)
    print("\ngespeichert -> daten_kurvenform.json")


if __name__ == "__main__":
    a = sys.argv[1:]
    ns = tuple(int(x) for x in a[0].split(",")) if len(a) > 0 else (40, 60, 80, 100)
    lauf(ns=ns,
        anzahl=int(a[1]) if len(a) > 1 else 200,
        seed=int(a[2]) if len(a) > 2 else 7)
daten_kurvenform.json, tabellarisch
nd(50%)Flächer_frühSteigung (partial)bestes Einzel-d
40+0.35-0.36-0.17-0.12d=6: -0.30
60+0.22-0.15+0.15-0.01d=16: -0.15
80+0.05-0.24-0.15+0.01d=10: -0.26
100+0.19-0.28+0.00-0.04d=12: -0.28

Frische Instanzen (α=4,267), r(d) über ein 10-Punkte-Gitter d∈[2,20] mit je 150 Stichproben. Gleiche Vorzeichen und Größenordnung wie im Text (Fläche negativ, d(50%) positiv korreliert), einzelne Werte weichen ab — eigene Stichprobe, gröberes Gitter, andere Saat. Datei ist echt lauffähig, nicht identisch reproduziert.

Rohdaten ansehen — daten_kurvenform.json
daten_kurvenform.json
{
 "40": {
  "n": 40,
  "unsat_instanzen": 73,
  "dgrid": [
   2,
   4,
   6,
   8,
   10,
   12,
   14,
   16,
   18,
   20
  ],
  "r_d50": 0.3479606073029222,
  "r_flaeche": -0.36251842134884277,
  "r_fruehe": -0.17370088667120148,
  "r_steigung": -0.05819098798997463,
  "r_steigung_partial": -0.11979069357446034,
  "bestes_d": 6,
  "bestes_d_r": -0.3020972629964603
 },
 "60": {
  "n": 60,
  "unsat_instanzen": 94,
  "dgrid": [
   2,
   4,
   6,
   8,
   10,
   12,
   14,
   16,
   18,
   20
  ],
  "r_d50": 0.2243167757534666,
  "r_flaeche": -0.15259385658657534,
  "r_fruehe": 0.15194037920638617,
  "r_steigung": 0.0402972934352017,
  "r_steigung_partial": -0.007313354762488981,
  "bestes_d": 16,
  "bestes_d_r": -0.14619166732875394
 },
 "80": {
  "n": 80,
  "unsat_instanzen": 85,
  "dgrid": [
   2,
   4,
   6,
   8,
   10,
   12,
   14,
   16,
   18,
   20
  ],
  "r_d50": 0.050865332976337885,
  "r_flaeche": -0.23906609449956126,
  "r_fruehe": -0.14568779293536763,
  "r_steigung": 0.015305609677908677,
  "r_steigung_partial": 0.008388184736164003,
  "bestes_d": 10,
  "bestes_d_r": -0.25859175329911815
 },
 "100": {
  "n": 100,
  "unsat_instanzen": 98,
  "dgrid": [
   2,
   4,
   6,
   8,
   10,
   12,
   14,
   16,
   18,
   20
  ],
  "r_d50": 0.18863854659675688,
  "r_flaeche": -0.2818229821799546,
  "r_fruehe": 0.0,
  "r_steigung": -0.005817917565504143,
  "r_steigung_partial": -0.03533360218296536,
  "bestes_d": 12,
  "bestes_d_r": -0.2792504496684058
 }
}

Interpretation

Die Ergebnisse deuten auf zwei unabhaengige Mechanismen hin:

  1. d(50%) — die Tiefe der Zirkularitaet. Welche Annahmetiefe braucht es, bis die Haelfte aller Stichproben faellt? Das ist die bekannte Korrelation.
  2. Steigung — die B ruechigkeit des Uebergangs. Eine steile Kurve (schneller Uebergang von niedriger zu hoher Rate) bedeutet: sobald die Schwelle ueberschritten ist, faellt die Formel schnell. Das ist ein separater, von d(50%) weitgehend unabhaengiger Mechanismus. Formeln mit steilerem Uebergang sind schwerer, auch bei gleichem d(50%).
  3. Fruehe Rate — Empfindlichkeit auf kleine Stoerungen. Eine Formel, die schon unter 4 Annahmen in 3.5% der Faelle kollabiert (n=60), ist im Mittel leichter als eine, bei der nur 3.0% kollabieren. Dieser Effekt ist von d(50%) teilweise unabhaengig.

Konsequenzen

Die Expansion-d(50%)-Bruecke existiert nicht. Geschlossene Mengen (d0_k > 0) sind in Zufalls-3-CNF an der Schwelle extrem selten. Die Expansion des Klauselhypergraphen korreliert negativ mit der Haerte (mehr Struktur = leichter), nicht positiv. Das ist der umgekehrte Effekt der urspruenglichen Hypothese.

Damit bleiben von GENESIS §7:

  1. Die Ordnung ausnutzen statt beschreiben. Die volle r(d)-Kurve traegt mehr Information als d(50%) allein. Eine Branching-Heuristik, die d(50%) UND die Steigung beruecksichtigt, koennte die Ausnutzungsluecke schliessen. Bisher scheiterte jede Uebersetzung (GENESIS §7).
  2. Frege beschraenkter Tiefe. Der Flaschenhals bleibt unangetastet. Gemessen: bis p ≈ 0.20 bei zufaelliger, bis p ≈ 0.13 bei gezielter Restriktion.
  3. Extension mit tragender Geschlossenheit. Keine neue Idee seit der GENESIS-Falle (§6).