Bisher ging jeder Weg in Richtung mehr Struktur. Zufall nimmt Struktur weg — also umgekehrt: die Strukturlosigkeit ins Extrem treiben und schauen, ob etwas anderes auftaucht.
Die Umkehrung hat in mehreren Fächern gewonnen. Ramsey-Theorie: „Vollständige Unordnung ist unmöglich“[1] — treibt man eine Färbung ins Zufällige, entsteht Struktur zwangsläufig. Szemerédis Regularitätslemma: jeder Graph zerfällt in beschränkt viele Stücke, die zufällig aussehen. Erdős' probabilistische Methode: Objekte konstruieren, indem man zeigt, dass ein zufälliges taugt. Konzentration des Maßes: in hoher Dimension wird Zufall wieder starr.
Der Fall, auf den es hier ankommt, hat aber genau an diesem Gegenstand stattgefunden. Mézard, Parisi und Zecchina haben nicht nach Struktur in der Formel gesucht, sondern gefragt, wie die Unordnung aussieht[2]. Antwort: der Lösungsraum zerfällt bei αd ≈ 3,86 in exponentiell viele Cluster[3], lange vor der Schwelle bei αs ≈ 4,267[4]. Diese Anti-Struktur war die Struktur, und Survey Propagation hat damit zufälliges 3-SAT mit einer Million Variablen nahe der Schwelle gelöst, als sonst nichts mehr ging.
Der Judo-Wurf ist also erlaubt — aber er erbt eine bekannte Halbblindheit. Das ist kein Grund, ihn nicht zu machen. Es ist der Grund, ihm vorher ein Abbruchkriterium mitzugeben statt einer Hoffnung.
Alle Beispiele oben nutzen den Zufall in der Instanz. Hier wandert er in das Messgerät — und das Gerät stand schon bereit: SAT im Phasenraum vermisst genau diesen Fluss, seine fraktalen Einzugsränder und die Beziehung α = κ/λ. Dort wurde er an einer Instanz verstanden; hier wird gefragt, ob er über viele Instanzen etwas über die Härte weiß. Ein Prozess wird auf der Formel laufen gelassen: ein Prozess wird auf der Formel laufen gelassen, und gemessen wird, wie unordentlich er sich benimmt. Das folgt einem Muster, das dieses Projekt zweimal selbst gemessen hat:
Im Repo ist dasselbe zweimal passiert: die stumpfe Koordinate — die Formel als eigener Ort — ist gestorben; die Front der Stromzählung, eine Prozessgröße, lebt. Das sind nicht zwei unabhängige Befunde, sondern ein Muster, das sich selbst bestätigt.
Sechs Sonden, und die entscheidende gemeinsame Eigenschaft: keine ruft einen Löser. Das ist Bedingung, nicht Zufall — im Handbuch trennt jedes gute Verfahren SAT von UNSAT nur deshalb, weil es Erfüllbarkeitsfragen stellt, und ist damit zirkulär.
| Sonde | Was sie misst |
|---|---|
| A Chaos | Die kontinuierliche Dynamik von Ercsey-Ravasz/Toroczkai[5] (Phasenraum). Auf UNSAT hat sie beweisbar keinen Attraktor — gemessen wird der Transient, mit festem Budget statt bis zur Lösung. |
| B Spektrum | Abstandsverhältnis der Eigenwerte[6]. Poisson 0,3863 (geordnet) gegen Wigner-Dyson 0,5307 (chaotisch)[7] — die kanonische Ordnung/Chaos-Sonde der Zufallsmatrixtheorie. |
| C Lokalisierung | Teilnahmeverhältnis der Klauselgewichte: konzentriert die Dynamik die Schuld auf wenige Klauseln oder verschmiert sie? |
| D Überlapp | Die q-Verteilung der Spingläser, aus abgeschnittenen Irrfahrten — Budget 12n, während eine Lösung im Median 1 990 Kippungen braucht. Sie darf nie entscheiden. |
| E Kompression | Unordnung als Inkompressibilität, getrennt nach Gerüst und Vorzeichen. |
| F Perkolation | Antwort der Einheitsfortpflanzung auf eine Störung — siehe unten. |
Die erste Fassung von Sonde F setzte eine Variable und maß die Lawine der Einheitsfortpflanzung. Ergebnis: immer exakt 1, ohne jede Streuung. Das war kein Fehler im Code, sondern ein Befund — bei α = 4,267 liegt zufälliges 3-SAT weit unter der Perkolationsschwelle der Einheitsfortpflanzung. Von einer Variable aus läuft nichts.
Statt den Parameter festzuhalten, wurde er durchgefahren. Und dann erscheint etwas:
Das ist der eigentliche Inhalt des Laufs. Wer 76 501 Kandidaten misst und den besten meldet, meldet das Maximum einer Stichprobe — und das ist auch dann deutlich von null verschieden, wenn jeder einzelne Kandidat reines Rauschen war.
Gemessen wird die partielle Rangkorrelation gegen log2(Konflikte), mit {n, σgrad2, sat} herausgerechnet. Die Gradstreuung trägt in diesem Projekt 42 % der Härtestreuung; sat herauszurechnen ist die schärfste Sperre, denn dass UNSAT teurer ist, ist bekannt — wer nur das wiederfindet, hat nichts gefunden.
Dieselbe Suche wurde achtmal komplett auf gemischtem Etikett gefahren. Die Schwelle für einen Fund ist das Maximum daraus, nicht die Null.
| Stufe | Mittel | Maximum |
|---|---|---|
| Stufe 1 (76 501 Kandidaten, nur lern) | 0,094 | 0,117 |
| Stufe 2 (beste 600 auf pruef) | 0,072 | 0,086 |
| gierig (4 Terme, Vorwärtsauswahl) | 0,101 | 0,130 |
def korr(v, H, zr):
"""|Rangkorrelation| von v mit dem schon residualisierten Ziel zr."""
r = B.rang(v)
r = r - H @ r
s = r.std()
if s < 1e-12:
return 0.0
return abs(float(np.dot(r, zr) / (len(r) * s * zr.std())))
# ------------------------------------------------------------ Suchraum
def paare(Q, hoechstens=None, rng=None):
ij = [(i, j) for i in range(Q) for j in range(i + 1, Q)]
if hoechstens and len(ij) > hoechstens:
rng = rng or np.random.default_rng(0)
pick = rng.choice(len(ij), hoechstens, replace=False)
ij = [ij[t] for t in pick]
return ij
def verbinde(X, i, j, art):
a, b = X[:, i], X[:, j]
if art == "quot":
n = np.abs(b)
return a / np.where(n < 1e-9, 1e-9, np.where(b < 0, -n, n))
if art == "prod":
return a * b
if art == "diff": # standardisierte Differenz
za = (a - a.mean()) / (a.std() + 1e-12)
zb = (b - b.mean()) / (b.std() + 1e-12)
return za - zb
raise ValueError(art)
ARTEN = ("quot", "prod", "diff")
def stufe1(X, H, zr, ij, arten=ARTEN, mitsingles=True):
"""Alle Kandidaten auf `lern` reihen. Gibt Liste (score, beschreibung)."""
aus = []
if mitsingles:
for i in range(X.shape[1]):
aus.append((korr(X[:, i], H, zr), ("einzeln", i, -1)))
for (i, j) in ij:
for art in arten:
aus.append((korr(verbinde(X, i, j, art), H, zr), (art, i, j)))
return aus
def baue(X, bez):
art, i, j = bez
return X[:, i] if art == "einzeln" else verbinde(X, i, j, art)
# ------------------------------------------------------------------ Lauf
def sammle_bank(substrate):
with Pool(min(22, os.cpu_count() or 4)) as p:
reihen = p.map(BK.bank, substrate, chunksize=16)
namen = sorted(set().union(*[set(r) for r in reihen]))
X = np.array([[r.get(k, 0.0) for k in namen] for r in reihen], np.float64)
X[~np.isfinite(X)] = 0.0
return namen, X
def z(v):
s = v.std()
return (v - v.mean()) / s if s > 1e-12 else np.zeros_like(v)
def gierig(Xl, Hl, zrl, Xp, Hp, zrp, bezuege, tiefe=4):
"""Vorwaertsauswahl: standardisierte Summen mehrerer Ausdruecke.
Rangkorrelation ist gegen monotone Umformungen blind, also kann ein
einzelner Ausdruck nicht mehr verbessert werden -- Zuwachs gibt es nur
durch WEITERE Terme. Genommen wird ein Term nur, wenn er min(lern,pruef)
hebt: wer nur auf lern hilft, ist Ueberanpassung und wird nicht genommen.
"""
if not bezuege:
return []
vl = {b: z(baue(Xl, b)) for b in bezuege}
vp = {b: z(baue(Xp, b)) for b in bezuege}
start = bezuege[0]
kette, sl, sp = [start], vl[start].copy(), vp[start].copy()
bestwert = min(korr(sl, Hl, zrl), korr(sp, Hp, zrp))
zeichen = [1.0]
spur = [(bestwert, tuple(kette), tuple(zeichen))]
for _ in range(tiefe - 1):
bester, bwert, bvz = None, bestwert, 1.0
for b in bezuege:
if b in kette:
continue
for vz in (1.0, -1.0):
w = min(korr(sl + vz * vl[b], Hl, zrl),
korr(sp + vz * vp[b], Hp, zrp))
if w > bwert + 1e-4:
bester, bwert, bvz = b, w, vz
if bester is None:
break
kette.append(bester)
zeichen.append(bvz)
sl += bvz * vl[bester]
sp += bvz * vp[bester]
bestwert = bwert
spur.append((bestwert, tuple(kette), tuple(zeichen)))
return spurJeder Punkt: dieselbe Suche über 76 501 Kandidaten, aber das Härte-Etikett zellenweise vertauscht. Das Maximum aus acht Durchgängen ist die Schwelle — nicht null.
{
"schwelle": 0.1301589930874898,
"s1schwelle": 0.11654299343656545,
"nullwerte": [
0.07307985475037881,
0.09803691914137302,
0.12620280753198415,
0.10369664996358074,
0.1301589930874898,
0.09224358597393721,
0.09736080529329685,
0.09094165030533212
],
"kandidaten": 76501,
"bank": 226
}Der gepoolte Wert überschätzt: n hat vier Stufen und sat zwei, und was rang-linear herausgerechnet wird, lässt eine nichtlineare Abhängigkeit stehen. Also wird jeder Kandidat zusätzlich innerhalb je einer festen (n, sat)-Zelle nachgerechnet, wo weder das eine noch das andere durchgreifen kann. Mitgemeldet wird, in wie vielen der acht Zellen das Vorzeichen dasselbe ist — ein echtes Merkmal zeigt überall in dieselbe Richtung, ein Artefakt wechselt.
Auf synthetischen Daten mit gepflanztem Signal, vor dem eigentlichen Lauf:
| bester Kandidat | auf test | Schwelle | Urteil | |
|---|---|---|---|---|
| Signal gepflanzt | 0,875 | 0,897 | 0,131 | gefunden, und als Verhältnis erkannt |
| kein Signal | 0,126 | 0,017 | 0,133 | korrekt verworfen |
Die zweite Zeile ist die wichtigere: die Suche erreicht auf reinem Rauschen 0,126 — und die Schwelle fängt es.
Dreißig Kandidaten lagen über der Schwelle. Die strenge Probe lässt davon einen als besten stehen:
| Merkmal | streng | Spanne (8 Zellen) | Vorzeichen |
|---|---|---|---|
| cSchuld.entropie allein | −0,043 | [−0,332, +0,314] | 4/8 |
| cTeil.teilnahme allein | +0,192 | [−0,018, +0,465] | 7/8 |
| Produkt der beiden | +0,259 | [+0,117, +0,481] | 8/8 |
Das Produkt trägt mehr als seine Teile. Die Entropie allein ist wertlos und wechselt in der Hälfte der Zellen das Vorzeichen; das Teilnahmeverhältnis allein liegt bei 0,192. Die Auskunft sitzt nicht in einer der beiden Koordinaten, sondern in ihrer Verbindung — genau das, wofür eine Suche über Verhältnisse und Produkte gebaut wird.
Der größere Teil der dreißig war Störgrößen-Neukombination, und die strenge Probe hat es gefunden:
| Baustein | r mit n | r mit sat |
|---|---|---|
| kGeruest (Kompression des Gerüsts) | +0,938 | −0,022 |
| kUeberhang | −0,937 | +0,020 |
| cSaettigung (Sättigung des Würfels) | −0,086 | +0,762 |
Die gierige Stufe verbindet mehrere Ausdrücke zu einer vorzeichenbehafteten Summe. Das Ergebnis ist der lehrreichste Teil des Laufs:
| Terme | gepoolt test | streng | Zellen gleich |
|---|---|---|---|
| 1 | 0,332 | +0,259 | 8/8 |
| 2 | 0,392 | +0,248 | 7/8 |
| 3 | 0,388 | +0,260 | 7/8 |
| 4 | 0,407 | +0,236 | 6/8 |
| 5 | 0,405 | +0,248 | 6/8 |
"""Die strenge (zellweise) Kennzahl für jede Stufe der gierigen Verbindung,
persistiert -- vorher nur in einem Wegwerfskript berechnet, nirgends
gespeichert. Das widerspricht der eigenen Regel: jede gemessene Aussage auf
der Seite muss zu einer Datei führen, die man ansehen und nachrechnen kann.
Liest sieb.json (Feld "gierig", die Bezeichner der einzelnen Terme) und die
Bank neu aus, baut jede Stufe als vorzeichenbehaftete Summe nach und
rechnet an jeder Stufe die Kontrolle aus HANDBUCH.md/JUDO.md: partielle
Rangkorrelation *innerhalb* jeder feste (n, sat)-Zelle, gemittelt, mit
Zählung der Zellen, die im selben Vorzeichen zeigen.
"""
import json, pickle, sys, os
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import bewerten as B
import sieb as SB
HIER = os.path.dirname(os.path.abspath(__file__))
DATEN = os.path.join(HIER, "daten")
def streng(v, nn, sat, gv, y, mindest=60):
ws, ns = [], []
for x in sorted(set(nn)):
for s_ in (0.0, 1.0):
m = (nn == x) & (sat == s_)
if m.sum() < mindest:
continue
ws.append(B.partiell(v[m], y[m], [gv[m]]))
ns.append(int(m.sum()))
if not ws:
return 0.0, 0, 0
ws, ns = np.array(ws), np.array(ns)
mit = float(np.average(ws, weights=ns))
return mit, int((np.sign(ws) == np.sign(mit)).sum()), len(ws)
import lauf as L
_TEIL_CACHE = {}
def _bauen(X, R, menge):
if not _TEIL_CACHE:
substrate = pickle.load(open(os.path.join(DATEN, "substrat.pkl"), "rb"))
satz, subs, idx = L.teile(R, substrate)
P = B.Pruefstand(satz)
for k in ("lern", "pruef", "test"):
Xk = X[idx[k]]
H = SB.hut(P.stoer[k], len(Xk))
zr0 = B.rang(P.ziel[k])
_TEIL_CACHE[k] = (Xk, H, zr0 - H @ zr0)
return _TEIL_CACHE[menge]
def lauf():
d = np.load(os.path.join(DATEN, "bank.npz"), allow_pickle=True)
X, namen = d["X"], list(d["namen"])
ni = {n: i for i, n in enumerate(namen)}
R = pickle.load(open(os.path.join(DATEN, "korpus.pkl"), "rb"))
sb = json.load(open(os.path.join(DATEN, "sieb.json")))
nn = np.array([r["n"] for r in R], float)
sat = np.array([r["sat"] for r in R], float)
gv = np.array([r["gradvar"] for r in R], float)
y = np.log2(np.maximum([r["konflikte"] for r in R], 1)).astype(float)
# WICHTIG: sieb.json speichert "gierig" nur mit tiefe=4 und dem Pool der
# besten 40 Stufe-2-Kandidaten (interner Aufruf in sieb.py::eine_suche).
# Die Tabelle auf judo.html hat aber 5 Stufen aus einem breiteren Pool --
# allen 30 Kandidaten aus sb["ergebnis"] statt nur den besten 40 aus
# Stufe 2 -- und tiefe=5. Um die exakt gezeigten Zahlen zu reproduzieren,
# muss hier derselbe (breitere) Pool und dieselbe Tiefe verwendet werden.
bezuege = [(e["art"], ni[e["a"]], ni[e["b"]]) for e in sb["ergebnis"]]
Xl_, Hl_, zrl_ = _bauen(X, R, "lern")
Xp_, Hp_, zrp_ = _bauen(X, R, "pruef")
Xt_, Ht_, zrt_ = _bauen(X, R, "test")
spur = SB.gierig(Xl_, Hl_, zrl_, Xp_, Hp_, zrp_, bezuege, tiefe=5)
aus = []
for wert, kette, zeichen in spur:
sl = sum(v * SB.z(SB.baue(Xl_, b)) for v, b in zip(zeichen, kette))
sp = sum(v * SB.z(SB.baue(Xp_, b)) for v, b in zip(zeichen, kette))
st = sum(v * SB.z(SB.baue(Xt_, b)) for v, b in zip(zeichen, kette))
rl, rp, rt = SB.korr(sl, Hl_, zrl_), SB.korr(sp, Hp_, zrp_), SB.korr(st, Ht_, zrt_)
ganz = sum(v * SB.z(SB.baue(X, b)) for v, b in zip(zeichen, kette))
sg, gl, zz = streng(ganz, nn, sat, gv, y)
txt = " ".join(("+" if v > 0 else "-") +
(namen[i] if a == "einzeln" else f"({namen[i]} {a} {namen[j]})")
for v, (a, i, j) in zip(zeichen, kette))
aus.append({"terme": len(kette), "lern": rl, "pruef": rp, "test": rt,
"streng": sg, "zellen_gleich": gl, "zellen": zz, "ausdruck": txt})
print(f" {len(kette)} Terme: lern {rl:.3f} pruef {rp:.3f} test {rt:.3f} "
f"streng {sg:+.3f} {gl}/{zz}")
with open(os.path.join(DATEN, "streng_je_term.json"), "w") as f:
json.dump(aus, f, indent=1)
print(f"\ngespeichert -> {os.path.join(DATEN, 'streng_je_term.json')}")
if __name__ == "__main__":
lauf()
| Terme | lern | pruef | test | streng | Zellen |
|---|---|---|---|---|---|
| 1 | 0.312 | 0.310 | 0.332 | +0.259 | 8/8 |
| 2 | 0.345 | 0.355 | 0.392 | +0.248 | 7/8 |
| 3 | 0.358 | 0.377 | 0.388 | +0.260 | 7/8 |
| 4 | 0.378 | 0.390 | 0.407 | +0.236 | 6/8 |
| 5 | 0.379 | 0.393 | 0.405 | +0.248 | 6/8 |
Die drei gepoolten Kurven steigen mit jedem Term. Die rote — die einzige, die gegen n, Gradstreuung und sat sowie zellenweise gerechnet ist — bleibt flach und fällt sogar. Das ist die Überanpassung, nicht der Fund.
[
{
"terme": 1,
"lern": 0.3121949716325861,
"pruef": 0.30971683757420543,
"test": 0.3324133787235236,
"streng": 0.25930672858491144,
"zellen_gleich": 8,
"zellen": 8,
"ausdruck": "+(cSchuld.entropie prod cTeil.teilnahme)"
},
{
"terme": 2,
"lern": 0.3446945783258858,
"pruef": 0.3547529368331009,
"test": 0.3919760659956465,
"streng": 0.24807174490592482,
"zellen_gleich": 7,
"zellen": 8,
"ausdruck": "+(cSchuld.entropie prod cTeil.teilnahme) -(cBogen.median diff cSchuldStreu.median)"
},
{
"terme": 3,
"lern": 0.35832565991185183,
"pruef": 0.37746030194027413,
"test": 0.38782528426719703,
"streng": 0.26004803841288204,
"zellen_gleich": 7,
"zellen": 8,
"ausdruck": "+(cSchuld.entropie prod cTeil.teilnahme) -(cBogen.median diff cSchuldStreu.median) -(cTeil.streu diff sL.teilnahme)"
},
{
"terme": 4,
"lern": 0.3776439765673545,
"pruef": 0.389714897199669,
"test": 0.407362902209816,
"streng": 0.23569579486940836,
"zellen_gleich": 6,
"zellen": 8,
"ausdruck": "+(cSchuld.entropie prod cTeil.teilnahme) -(cBogen.median diff cSchuldStreu.median) -(cTeil.streu diff sL.teilnahme) +(cK.teilnahme quot kUeberhang)"
},
{
"terme": 5,
"lern": 0.3794966148490839,
"pruef": 0.39318464523725827,
"test": 0.404828812015732,
"streng": 0.24841559524734894,
"zellen_gleich": 6,
"zellen": 8,
"ausdruck": "+(cSchuld.entropie prod cTeil.teilnahme) -(cBogen.median diff cSchuldStreu.median) -(cTeil.streu diff sL.teilnahme) +(cK.teilnahme quot kUeberhang) -(cSchuld.streu quot cSpurEnde.teilnahme)"
}
]Wer hier nach der gepoolten Zahl optimiert hätte, hätte einen Viertermausdruck mit 0,41 gemeldet, der weniger weiß als der Einterm mit 0,33. Gepooltes und strenges Maß laufen unter gieriger Suche auseinander, und nur das strenge ist belastbar.
Der ursprüngliche Plan war, ein lokales Sprachmodell als Mutationsoperator laufen zu lassen. Gemessen: die Radeon 890M hat 512 MiB eigenen Speicher, alles weitere läuft über GTT und damit über denselben DDR5-Bus wie die CPU. Kein Bandbreitenvorteil, 12 bis 15 Zeichen je Sekunde — unabhängig von der Modellgröße, ein 4B-Modell war so langsam wie ein 35B.
Also umgebaut: die Breite kommt vom Sieb (alle Paare vollständig), das Modell bekommt den Teil, der zu ihm passt — eine Zeile je Vorschlag statt eines Codeblocks, und nur Verbindungen ab drei Namen, deren Raum zu groß zum Abzählen ist. In drei Stunden: 3 551 Kandidaten, davon 3 514 nach Entdopplung.
Die eigene Vertauschungsnull der Einspritzung — dieselben 3 514 Ausdrücke gegen ein innerhalb der Zellen gemischtes Etikett — liegt bei 0,067, mit null Falschmarkierungen in vier Durchgängen. Die strenge Statistik mit Zellenkonsistenz ist ein deutlich schärferes Instrument als die gepoolte.
Gegen das Handbuch, Teil IV: die dort verzeichneten löserfreien Zugänge liegen bei r ≤ 0,19; d(50 %) erreicht 0,56 und braucht einen Löser. Der Fund liegt damit über allem bisher Löserfreien — mit dem Vorbehalt, dass nicht geprüft ist, ob die Zahlen der Tabelle mit denselben Störgrößen gerechnet wurden.
Ein praktisch brauchbarer Härteschätzer ist 0,28 nicht. Er erklärt rund acht Prozent der Härtestreuung.
Eine Frage blieb offen, und es war die unangenehmste. Die Einspritzung wurde über 497 Runden von einer Bestenliste gesteuert, die nach genau der Größe gereiht war, mit der sie am Ende bewertet wird — und diese Größe lief über alle Instanzen. Es gab also keine zurückgehaltene Menge, die den Rückkopplungsweg unterbrochen hätte.
Die Vertauschungsprobe beantwortet das nicht. Sie mischt das Etikett und rechnet eine feste Kandidatenmenge nach; sie preist damit „ist der Wert dieses Ausdrucks echt“, nicht „hat die Steuerung ihn hochgezogen“. Das eine lässt sich nur mit Instanzen beantworten, die es während der Suche noch nicht gab.
Also: 900 frische Instanzen, andere Saaten, dasselbe Substrat, dieselben Ausdrücke — und keine Anpassung mehr. Drei Größen n statt vier, daher sechs Zellen statt acht.
| Ausdruck | frisch | Zellen | vorher |
|---|---|---|---|
| Sieb, bester Paarausdruck | +0,266 | 6/6 | +0,259 |
| Einspritzung, bester | +0,238 | 6/6 | +0,283 |
| Einspritzung, zweitbester | +0,222 | 6/6 | +0,273 |
| Einspritzung, dritter | +0,225 | 6/6 | +0,272 |
| cTeil.teilnahme allein | +0,211 | 6/6 | +0,192 |
| cSchuldStreu.schiefe allein | −0,123 | 4/6 | — |
| Kontrolle: n-Stellvertreter | +0,009 | 3/6 | +0,013 |
| Kontrolle: sat-Stellvertreter | −0,037 | 3/6 | −0,040 |
Drei Dinge stehen damit fest.
Bemerkenswert nebenbei: cSchuldStreu.schiefe, der Baustein, der in acht der zehn besten Einspritzungsausdrücke steckt, ist allein schwach und uneinheitlich (−0,123 bei 4/6). Dasselbe Muster wie beim Sieb — die Auskunft sitzt in der Verbindung, nicht im Baustein.
Der Fund, auf frischen Instanzen bestätigt:
cSchuld.entropie × cTeil.teilnahme → r = 0,266
Löserfrei, gegen n, Gradstreuung und Erfüllbarkeit abgesichert, in allen sechs Zellen gleichgerichtet, auf 900 nie gesehenen Instanzen bestätigt. Rauschschwelle der Suche: 0,130.
Gegen das Handbuch, Teil IV: die dort verzeichneten löserfreien Zugänge liegen bei r ≤ 0,19; d(50 %) erreicht 0,56 und braucht einen Löser. Ein praktisch brauchbarer Härteschätzer ist 0,27 nicht — er erklärt rund sieben Prozent der Streuung.
"""Die letzte Probe: ein Korpus, den nichts je gesehen hat.
WARUM SIE NOETIG IST. Die Einspritzung wurde ueber 497 Runden von einer
Bestenliste gesteuert, die nach genau der Groesse gereiht war, mit der sie
am Ende bewertet wird -- und diese Groesse lief ueber ALLE Instanzen. Es
gab also keine zurueckgehaltene Menge, die den Rueckkopplungsweg
unterbrochen haette.
Die Vertauschungsprobe beantwortet das nicht. Sie mischt das Etikett und
rechnet eine FESTE Kandidatenmenge nach; sie preist damit "ist der Wert
dieses Ausdrucks echt", nicht "hat die Steuerung ihn hochgezogen". Das eine
laesst sich nur mit Instanzen beantworten, die es waehrend der Suche noch
nicht gab.
Also: frische Instanzen, andere Saat, dasselbe Substrat, dieselben
Ausdruecke -- und keine Anpassung mehr.
"""
import glob, json, os, pickle, sys, time
from multiprocessing import Pool
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import bank as BK, bewerten as B, einspritzung as E, substrat as S
HIER = os.path.dirname(os.path.abspath(__file__))
def eine(a):
i, r = a
return i, BK.bank(S.alles(r["klauseln"], r["n"], saat=7000 + i))
def lauf():
R = []
for f in sorted(glob.glob(os.path.join(HIER, "frisch", "korpus_n*.jsonl"))):
for z in open(f):
z = z.strip()
if z:
try: R.append(json.loads(z))
except json.JSONDecodeError: pass
for r in R:
c = np.zeros(r["n"], np.int64)
for k in r["klauseln"]:
for l in k: c[abs(l) - 1] += 1
r["gradvar"] = float(c.var())
print(f"# {len(R)} frische Instanzen, SAT-Anteil {np.mean([r['sat'] for r in R]):.3f}",
flush=True)
t0 = time.time()
aus = [None] * len(R)
with Pool(min(22, os.cpu_count() or 4)) as p:
for i, b in p.imap_unordered(eine, list(enumerate(R)), chunksize=4):
aus[i] = b
print(f"# Substrat+Bank in {(time.time()-t0)/60:.1f} min", flush=True)
alt = np.load(os.path.join(HIER, "daten", "bank.npz"), allow_pickle=True)
namen = list(alt["namen"])
X = np.array([[b.get(k, 0.0) for k in namen] for b in aus], np.float64)
X[~np.isfinite(X)] = 0.0
ni = {n: i for i, n in enumerate(namen)}
nn = np.array([r["n"] for r in R], float)
sat = np.array([r["sat"] for r in R], float)
gv = np.array([r["gradvar"] for r in R], float)
y = np.log2(np.maximum([r["konflikte"] for r in R], 1)).astype(float)
zellen = [m for m in ((nn == x) & (sat == s)
for x in sorted(set(nn)) for s in (0.0, 1.0))
if m.sum() >= 40]
gr = np.array([m.sum() for m in zellen], float)
def streng(v):
w = np.array([B.partiell(v[m], y[m], [gv[m]]) for m in zellen])
mit = float((w * gr).sum() / gr.sum())
return mit, int((np.sign(w) == np.sign(mit)).sum()), len(w)
KAND = [
("Sieb, bester Paarausdruck",
"cSchuld.entropie * cTeil.teilnahme", 0.259),
("Einspritzung, bester",
"cSchuldStreu.schiefe * wE.rate_frueh * sLAbst.median / cTeil.streu", 0.283),
("Einspritzung, zweitbester",
"cSchuldStreu.schiefe * wE.rate_frueh / cTeil.streu", 0.273),
("Einspritzung, dritter",
"cSchuldStreu.schiefe * wE.rate_frueh * sLAbst.median / cTeil.vk", 0.272),
("nur der Hauptbaustein", "cSchuldStreu.schiefe", None),
("nur cTeil.teilnahme", "cTeil.teilnahme", 0.192),
("Artefakt zur Kontrolle (n-Stellvertreter)", "kGeruest", 0.013),
("Artefakt zur Kontrolle (sat-Stellvertreter)", "cSaettigung", -0.040),
]
print(f"\n {'':44s} {'frisch':>8s} {'Zellen':>7s} {'alt':>7s}")
for nam, ausdr, alt_ in KAND:
try:
v = E.werte_aus(ausdr, X, ni)
except Exception as ex:
print(f" {nam:44s} -- {ex}"); continue
m, g, z = streng(v)
av = f"{alt_:+.3f}" if alt_ is not None else " - "
print(f" {nam:44s} {m:+8.3f} {g}/{z:<4d} {av}")
print(f"\n (Zellen = wieviele der {len(zellen)} (n,sat)-Zellen dasselbe "
f"Vorzeichen zeigen)")
if __name__ == "__main__":
lauf()
| Ausdruck | frisch | Zellen | Sieb/altes Ergebnis |
|---|---|---|---|
| Sieb, bester Paarausdruck | +0,266 | 6/6 | +0,259 |
| Einspritzung, bester | +0,238 | 6/6 | +0,283 |
| Einspritzung, zweitbester | +0,222 | 6/6 | +0,273 |
| Einspritzung, dritter | +0,225 | 6/6 | +0,272 |
| nur der Hauptbaustein | −0,123 | 4/6 | — |
| nur cTeil.teilnahme | +0,211 | 6/6 | +0,192 |
| Kontrolle: n-Stellvertreter | +0,009 | 3/6 | +0,013 |
| Kontrolle: sat-Stellvertreter | −0,037 | 3/6 | −0,040 |
900 Instanzen mit frischen Saaten, SAT-Anteil 0,509, Substrat in 14,9 min neu gerechnet. „Zellen“ = wie viele der 6 (n, sat)-Zellen dasselbe Vorzeichen zeigen.
# 900 frische Instanzen, SAT-Anteil 0.509
# Substrat+Bank in 14.9 min
frisch Zellen alt
Sieb, bester Paarausdruck +0.266 6/6 +0.259
Einspritzung, bester +0.238 6/6 +0.283
Einspritzung, zweitbester +0.222 6/6 +0.273
Einspritzung, dritter +0.225 6/6 +0.272
nur der Hauptbaustein -0.123 4/6 -
nur cTeil.teilnahme +0.211 6/6 +0.192
Artefakt zur Kontrolle (n-Stellvertreter) +0.009 3/6 +0.013
Artefakt zur Kontrolle (sat-Stellvertreter) -0.037 3/6 -0.040
(Zellen = wieviele der 6 (n,sat)-Zellen dasselbe Vorzeichen zeigen)
Die Messungen dieser Seite stammen aus judo/ im Projektarchiv: substrat.py (Sonden), bank.py (Merkmalsbank), sieb.py (Suche und Vertauschungsnull), bewerten.py (Störgrößen), frischprobe.py (die frischen Instanzen). Der Volltext liegt als JUDO.md bei.