English

Bahnen — fünf Tafeln

Aus daten_bahn/: 692 MB, zehn Instanzen, bis zu 20 006 Sortierungen je Instanz, jede Klausel ein Messpunkt. Alle Tafeln direkt aus den Rohdaten gerechnet.

1 · Der Fächer

Dichte des Backbone-Anteils gegen den Abstand zum Kipppunkt, über 20 000 Zufallssortierungen einer Formel (n = 30). Die rote Linie ist der Mittelwert.

45 35 25 15 10 5 0 0 0.25 0.5 0.75 1.0 Klauseln vor dem Kipppunkt Backbone-Anteil
Bis etwa 40 Klauseln vor dem Kippen ist nichts eingefroren. Danach fächert es auf — dieselbe Formel liegt je nach Reihenfolge irgendwo zwischen 0 und 1. Der Mittelwert verdeckt genau diese Streuung.
Code & Daten ansehen — die vollständigen Kennzahlen und ein Rohdaten-Auszug
bahn.py — die Bahn statt der Momentaufnahme
"""
Die BAHN statt der Momentaufnahme.

MATHIAS' KORREKTUR. Uebersetzt man Klausel fuer Klausel in Geometrie, so
kann die Loesungsmenge erst leer sein, wenn die letzte relevante Klausel
die Erfuellbarkeit kippt. Bis dahin ist sie per Definition nichtleer. Der
Gegenstand ist also die VERLAUFSKURVE, nicht der Endpunkt — und
restgeometrie.py hat nur eine Momentaufnahme gemessen.

Das macht den dortigen Befund erst scharf: einen Schritt vor dem Kippen
sind nur noch 5,3 Variablen frei. Der Zusammenbruch ist also nicht das Werk
der letzten Klausel. WO passiert er?

VERFAHREN. Klauseln in der Erzeugungsreihenfolge hinzufuegen, den Kipppunkt
k* per Bisektion finden, und dann die letzten Schritte davor vermessen:
   spann     Zahl der Variablen, die ueber 20 Loesungen variieren
   hamming   mittlerer paarweiser Abstand
   loesungen wieviele der 20 verlangten ueberhaupt existieren

Loesungen exakt ueber Blockierklauseln, kein Absuchen.

VORHERSAGE, VOR DEM LAUF NOTIERT. Die Kurve wird von der Dichte bestimmt
und daher zwischen Instanzen aehnlich sein; instanzspezifische Information
steckt allenfalls in der ABWEICHUNG von der mittleren Kurve. Ob die die
Haerte vorhersagt, ist die eigentliche Frage.
"""
import os, sys, pickle, resource
import numpy as np
from restgeometrie import loesungen_sammeln, rang


def kipppunkt(cl, n, budget=8000000):
    """Kleinstes k, sodass die ersten k Klauseln unerfuellbar sind."""
    from pysat.solvers import Cadical153
    lo, hi = 1, len(cl)
    while lo < hi:
        mid = (lo + hi) // 2
        so = Cadical153(bootstrap_with=cl[:mid]); so.conf_budget(budget)
        r = so.solve_limited(); so.delete()
        if r is False:
            hi = mid
        else:
            lo = mid + 1
    return lo


def geometrie(cl, n, k=20):
    L = loesungen_sammeln(cl, n, k)
    if len(L) < 2:
        return {"spann": 0, "hamming": 0.0, "loesungen": len(L)}
    A = np.array(L)
    d = [np.sum(A[a] != A[b]) for a in range(len(A)) for b in range(a + 1, len(A))]
    return {"spann": int((A.max(0) != A.min(0)).sum()),
            "hamming": float(np.mean(d)), "loesungen": len(L)}


if __name__ == "__main__":
    resource.setrlimit(resource.RLIMIT_AS, (3500 * 1024 * 1024,) * 2)
    resource.setrlimit(resource.RLIMIT_CPU, (3300, 3300))
    from pysat.solvers import Cadical153
    from math import sqrt, erfc
    SCR = os.environ["SCR"]
    with open(os.path.join(SCR, "instanzen.pkl"), "rb") as f:
        CLS = pickle.load(f)
    N, ZIEL = 100, 30
    RUECK = [40, 30, 20, 15, 10, 6, 3, 2, 1]
    print("Kipppunkt suchen und die Bahn davor vermessen, %d Instanzen, n = %d" % (ZIEL, N))
    K, BAHN, KST = [], [], []
    for i, (cl, R) in enumerate(CLS[:ZIEL]):
        so = Cadical153(bootstrap_with=cl); so.conf_budget(20000000)
        so.solve_limited(); K.append(so.accum_stats().get("conflicts", 0)); so.delete()
        ks = kipppunkt(cl, N)
        KST.append(ks)
        z = {}
        for r in RUECK:
            if ks - r < 3:
                continue
            z[r] = geometrie(cl[:ks - r], N)
        BAHN.append(z)
        if (i + 1) % 10 == 0:
            sys.stdout.write(" %d" % (i + 1)); sys.stdout.flush()
    print()
    print()
    print("=" * 88)
    print("DIE BAHN: Geometrie r Klauseln VOR dem Kipppunkt")
    print("k* liegt im Median bei %d von %d Klauseln (%.1f %%)"
          % (np.median(KST), len(CLS[0][0]), 100 * np.median(KST) / len(CLS[0][0])))
    print("=" * 88)
    print("%-10s %10s %12s %12s %14s" % ("r", "Instanzen", "Spann", "Hamming", "Loesungen"))
    for r in RUECK:
        v = [b[r] for b in BAHN if r in b]
        if not v:
            continue
        print("%-10d %10d %12.1f %12.2f %14.1f"
              % (r, len(v), np.mean([x["spann"] for x in v]),
                 np.mean([x["hamming"] for x in v]),
                 np.mean([x["loesungen"] for x in v])))
    print("=" * 88)
    print()
    y = np.log2(np.maximum(np.array(K, float), 1))
    print("Sagt die BAHN die Widerlegungskosten vorher?  (Massstab d(50 %) = +0,56)")
    print("%-22s %11s %11s %10s" % ("Groesse", "Pearson", "Spearman", "p"))
    kand = {}
    for r in (20, 10, 3):
        for nm in ("spann", "hamming"):
            w = [(b[r][nm] if r in b else np.nan) for b in BAHN]
            kand["%s bei r=%d" % (nm, r)] = w
    # Steilheit des Zusammenbruchs: Spann bei r=20 minus Spann bei r=3
    kand["Abfall Spann 20->3"] = [(b[20]["spann"] - b[3]["spann"])
                                  if (20 in b and 3 in b) else np.nan for b in BAHN]
    kand["k* selbst"] = [float(x) for x in KST]
    for nm, w in kand.items():
        w = np.array(w, float)
        gut = ~np.isnan(w)
        if gut.sum() < 10 or w[gut].std() < 1e-9:
            print("%-22s   (zu wenige/konstant)" % nm); continue
        pr = np.corrcoef(w[gut], y[gut])[0, 1]
        sp_ = np.corrcoef(rang(w[gut]), rang(y[gut]))[0, 1]
        p_ = erfc(abs(pr) * sqrt(gut.sum() - 3) / sqrt(2))
        print("%-22s %+10.4f %+11.4f %10.4f%s"
              % (nm, pr, sp_, p_, "  <-" if p_ < 0.05 else ""))
        sys.stdout.flush()
daten_bahn/kennzahlen_*.csv, alle 10 Instanzen vollständig
DateinmCDCL-Konflikted(50%)|R|R-Anteil
n100_i110042784311.5180150.03513
n100_i210042767011.290460.01405
n140_i1140598299815.287200.00000
n140_i2140598274015.4087260.04348
n30_i130128314.658990.07031
n30_i230128294.477640.03125
n50_i150213636.624830.01408
n50_i250213666.547710.00469
n80_i1803424829.7937190.05556
n80_i2803423169.510930.00877

Vollständige Kennzahlen aller 10 Instanzen (2 je n ∈ {30,50,80,100,140}) — die einzige Ebene der 692 MB, die klein genug zum Einbetten ist.

Rohdaten ansehen — daten_bahn/kennzahlen_*.csv (zusammengeführt)
daten_bahn/kennzahlen_*.csv (zusammengeführt)
n,m,alpha,cdcl_konflikte,d50,R_anzahl,R_anteil,bC_mittel,bC_min,bC_max,sp_konv,sp_runden,sp_eta_mittel,sp_joker,sp_bias,sortierungen_zufall,sortierungen_gezielt
100,427,4.27,843,11.5180,15,0.03513,0.9033,0.8500,0.9400,0,2000,0.20968,0.10769,0.52097,3000,6
100,427,4.27,670,11.2904,6,0.01405,0.8300,0.6400,0.9400,1,170,0.20873,0.12726,0.39213,3000,6
140,598,4.27,2998,15.2872,0,0.00000,,,,0,2000,0.23622,0.09529,0.37168,500,6
140,598,4.27,2740,15.4087,26,0.04348,0.9333,0.9214,0.9429,1,122,0.19351,0.14551,0.40951,500,6
30,128,4.27,31,4.6589,9,0.07031,0.9111,0.8667,0.9667,1,309,0.24064,0.09424,0.35464,20000,6
30,128,4.27,29,4.4776,4,0.03125,0.9778,0.9667,1.0000,1,395,0.26895,0.06843,0.26829,20000,6
50,213,4.27,63,6.6248,3,0.01408,0.9467,0.9200,0.9600,0,2000,0.23420,0.09666,0.49654,15000,6
50,213,4.27,66,6.5477,1,0.00469,0.7800,0.7800,0.7800,1,200,0.22146,0.11572,0.40301,15000,6
80,342,4.27,482,9.7937,19,0.05556,0.9042,0.8500,0.9625,1,138,0.22085,0.10957,0.42749,8000,6
80,342,4.27,316,9.5109,3,0.00877,0.9042,0.9000,0.9125,0,2000,0.23126,0.08497,0.50138,8000,6
daten_bahn/bahn_n30_i1.csv — Auszug (Datei hat 2.432.096 Zeilen. 91 MB)
sortierung_id,sortierung_art,k,klausel_index,klausel_in_R,backbone_abs,backbone_anteil,dbackbone,modelle,unsat
0,erzeugung,1,0,0,0,0.0000,0,2,0
0,erzeugung,2,1,0,0,0.0000,0,29,0
0,erzeugung,3,2,0,0,0.0000,0,16,0
0,erzeugung,4,3,0,0,0.0000,0,16,0
0,erzeugung,5,4,0,0,0.0000,0,16,0
0,erzeugung,6,5,0,0,0.0000,0,17,0
0,erzeugung,7,6,1,0,0.0000,0,16,0
0,erzeugung,8,7,0,0,0.0000,0,16,0
0,erzeugung,9,8,0,0,0.0000,0,16,0
0,erzeugung,10,9,0,0,0.0000,0,16,0
0,erzeugung,11,10,0,0,0.0000,0,14,0
0,erzeugung,12,11,0,0,0.0000,0,4,0
0,erzeugung,13,12,0,0,0.0000,0,5,0
0,erzeugung,14,13,0,0,0.0000,0,7,0
0,erzeugung,15,14,0,0,0.0000,0,8,0

Die vollständigen Rohdaten (692 MB. 10 Dateien à bis 91 MB. insgesamt Millionen Zeilen — je Klausel einer Sortierung eine Zeile) sind zu groß zum Einbetten. Oben ein echter Auszug der ersten 15 von 2.432.096 Zeilen einer Datei. Neu erzeugen: python3 bahn.py — dauert je nach n von Minuten (n=30) bis Stunden (n=140).

2 · Die Lawinen

Grössenverteilung der Backbone-Zuwächse, doppelt logarithmisch. Eine Gerade in dieser Darstellung ist ein Potenzgesetz.

1 3 10 30 100 10⁻⁵ 10⁻⁴ 10⁻³ 10⁻² 10⁻¹ 1 Groesse der Lawine (eingefrorene Variablen) relative Haeufigkeit Steigung −1,3 n = 30 n = 50 n = 80 n = 100
Vier Instanzen, vier n — alle folgen derselben Geraden. Die gestrichelte Referenz hat Steigung −1,3. Der Schwanz reicht bis zu Lawinen, die fast alle Variablen auf einmal einfrieren.

3 · Wo die Lawinen sitzen

Anteil der Zuwächse, die gross sind (≥ n/10), gegen den Abstand zum Kipppunkt.

45 35 25 15 10 5 0 0 20 40 60 Klauseln vor dem Kipppunkt Anteil grosser Lawinen (%) n = 30 (gross ≥ 3) n = 50 (gross ≥ 5) n = 100 (gross ≥ 10)
Die Anfälligkeit wächst glatt zum Kipppunkt hin — aber grosse Lawinen gibt es im ganzen Bereich, nicht nur am Ende. Das unterscheidet kritische Dynamik von einem einzelnen Einbruch.

4 · Der Kipppunkt einer einzigen Formel

Verteilung von k* über 20 000 Sortierungen derselben Instanz.

95 100 105 110 115 120 125 0 0.042 0.085 Kipppunkt k* (von 128 Klauseln) Anteil der Sortierungen Mittel 121.6
Dieselbe Formel kippt je nach Reihenfolge zwischen der 97. und der 128. Klausel. Linksschief und an der Obergrenze gestaut — die Bahn ist zu grossen Teilen eine Eigenschaft der Anordnung.

5 · Drei Populationen

Anteil der Sortierungen mit lockerem, mittlerem und eingefrorenem Backbone, gegen den Abstand zum Kipppunkt.

30 25 20 15 10 5 0 0 25 50 75 100 Klauseln vor dem Kipppunkt Anteil der Sortierungen (%) locker ≤ 0,40 Mitte eingefroren ≥ 0,80
Die Mitte wird nie leer. Kein Gabeln in zwei Äste, sondern ein Plateau, das sich in den eingefrorenen Gipfel entleert — deshalb ist es keine Bifurkation im wörtlichen Sinn.