summaryrefslogtreecommitdiff
path: root/experiments
diff options
context:
space:
mode:
Diffstat (limited to 'experiments')
-rw-r--r--experiments/run_ablation_20seeds.py6
-rw-r--r--experiments/run_bp_kaft_depth.py (renamed from experiments/run_bp_graft_depth.py)12
-rw-r--r--experiments/run_combo_20seeds.py72
-rw-r--r--experiments/run_cora_perturb.py4
-rw-r--r--experiments/run_cs_full.py14
-rw-r--r--experiments/run_dblp_depth.py8
-rw-r--r--experiments/run_dblp_depth_scaling.py14
-rw-r--r--experiments/run_depth_extras.py6
-rw-r--r--experiments/run_dfagnn_depth.py4
-rw-r--r--experiments/run_grad_reach_20seeds.py22
-rw-r--r--experiments/run_hero_extras.py12
-rw-r--r--experiments/run_realworld_hero_L20.py18
-rw-r--r--experiments/run_resgcn_20seeds.py20
-rw-r--r--experiments/run_shallow_depth.py20
-rw-r--r--experiments/run_wikics_paper_setup.py14
15 files changed, 122 insertions, 124 deletions
diff --git a/experiments/run_ablation_20seeds.py b/experiments/run_ablation_20seeds.py
index 61055ed..d2bd434 100644
--- a/experiments/run_ablation_20seeds.py
+++ b/experiments/run_ablation_20seeds.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Ablation study with 20 seeds: BP → DFA → DFA-GNN → VanillaGrAPE → GRAFT."""
+"""Ablation study with 20 seeds: BP → DFA → DFA-GNN → VanillaGrAPE → KAFT."""
import torch
import numpy as np
@@ -7,7 +7,7 @@ import json
import os
from scipy import stats as scipy_stats
from src.data import load_dataset
-from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, VanillaGrAPETrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, VanillaGrAPETrainer, KAFTTrainer
device = 'cuda:0'
SEEDS = list(range(20))
@@ -22,7 +22,7 @@ METHODS = {
'diffusion_alpha': 0.5, 'diffusion_iters': 10,
'lr_feedback': 0.5, 'num_probes': 64, 'topo_mode': 'fixed_A'
}),
- 'GRAFT': (GraphGrAPETrainer, {
+ 'KAFT': (KAFTTrainer, {
'diffusion_alpha': 0.5, 'diffusion_iters': 10,
'lr_feedback': 0.5, 'num_probes': 64, 'topo_mode': 'fixed_A'
}),
diff --git a/experiments/run_bp_graft_depth.py b/experiments/run_bp_kaft_depth.py
index 1e8dd76..d3119e1 100644
--- a/experiments/run_bp_graft_depth.py
+++ b/experiments/run_bp_kaft_depth.py
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
-"""H9: BP + GRAFT depth sweep on Cora/CiteSeer/PubMed.
+"""H9: BP + KAFT depth sweep on Cora/CiteSeer/PubMed.
E1 already did DBLP L={8,12,16,20,24,32}. This fills the gap for Cora/CiteSeer/PubMed
at L={8,10,12,16,20} so we can plot Figure 4(a)-style depth curves on 4 datasets.
-BP + GRAFT only (GRAFT+ResGCN not needed for this figure — that's stacking table).
+BP + KAFT only (KAFT+ResGCN not needed for this figure — that's stacking table).
"""
import torch
@@ -12,20 +12,20 @@ import numpy as np
import json
import os
from src.data import load_dataset
-from src.trainers import BPTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, KAFTTrainer
device = 'cuda:0'
SEEDS = list(range(20))
EPOCHS = 200
DEPTHS = [8, 10, 12, 16, 20]
-OUT_DIR = 'results/bp_graft_depth_20seeds'
+OUT_DIR = 'results/bp_kaft_depth_20seeds'
grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10,
lr_feedback=0.5, num_probes=64, topo_mode='fixed_A')
METHODS = {
'BP': (BPTrainer, {}),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
}
@@ -90,7 +90,7 @@ def main():
del data; torch.cuda.empty_cache()
# Summary
- print(f"\n{'=' * 70}\nBP/GRAFT depth sweep summary\n{'=' * 70}")
+ print(f"\n{'=' * 70}\nBP/KAFT depth sweep summary\n{'=' * 70}")
results = {}
for ds in datasets_cfg:
print(f"\n{ds}:")
diff --git a/experiments/run_combo_20seeds.py b/experiments/run_combo_20seeds.py
index 1598964..acc8846 100644
--- a/experiments/run_combo_20seeds.py
+++ b/experiments/run_combo_20seeds.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
-"""Task 2ceadaa7: GRAFT + Forward Tricks combo experiments (20 seeds).
+"""Task 2ceadaa7: KAFT + Forward Tricks combo experiments (20 seeds).
-Combos: GRAFT+ResGCN, GRAFT+DropEdge, GRAFT+PairNorm, GRAFT+JKNet
+Combos: KAFT+ResGCN, KAFT+DropEdge, KAFT+PairNorm, KAFT+JKNet
Each compared to: BP, forward_trick_only, GRAFT_only, combo
"""
@@ -12,7 +12,7 @@ import json
import os
from scipy import stats as scipy_stats
from src.data import load_dataset, spmm, build_normalized_adj
-from src.trainers import BPTrainer, GraphGrAPETrainer, _FeedbackTrainerBase
+from src.trainers import BPTrainer, KAFTTrainer, _FeedbackTrainerBase
from run_deep_baselines import ResGCNTrainer, JKNetTrainer
from run_dropedge import BPDropEdgeTrainer
from run_pairnorm_baseline import BPPairNormTrainer, pairnorm
@@ -28,10 +28,10 @@ grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10,
# ═══════════════════════════════════════════════════════════════════════════
-# GRAFT + ResGCN combo (fixed version)
+# KAFT + ResGCN combo (fixed version)
# ═══════════════════════════════════════════════════════════════════════════
-class GRAFTResGCN(GraphGrAPETrainer):
- """GRAFT backward + ResGCN forward (skip connections)."""
+class GRAFTResGCN(KAFTTrainer):
+ """KAFT backward + ResGCN forward (skip connections)."""
def forward(self):
X = self.data['X']
@@ -57,10 +57,10 @@ class GRAFTResGCN(GraphGrAPETrainer):
# ═══════════════════════════════════════════════════════════════════════════
-# GRAFT + DropEdge combo
+# KAFT + DropEdge combo
# ═══════════════════════════════════════════════════════════════════════════
-class GRAFTDropEdge(GraphGrAPETrainer):
- """GRAFT backward + DropEdge forward (random edge dropping)."""
+class GRAFTDropEdge(KAFTTrainer):
+ """KAFT backward + DropEdge forward (random edge dropping)."""
def __init__(self, *args, drop_rate=0.5, **kwargs):
super().__init__(*args, **kwargs)
@@ -81,9 +81,9 @@ class GRAFTDropEdge(GraphGrAPETrainer):
).coalesce()
def forward(self):
- # DropEdge only in forward pass, GRAFT backward uses original A_hat
+ # DropEdge only in forward pass, KAFT backward uses original A_hat
self.data['A_hat'] = self._drop_edges()
- result = super().forward() # uses GraphGrAPETrainer.forward()
+ result = super().forward() # uses KAFTTrainer.forward()
self.data['A_hat'] = self._A_hat_orig
return result
@@ -93,10 +93,10 @@ class GRAFTDropEdge(GraphGrAPETrainer):
# ═══════════════════════════════════════════════════════════════════════════
-# GRAFT + PairNorm combo
+# KAFT + PairNorm combo
# ═══════════════════════════════════════════════════════════════════════════
-class GRAFTPairNorm(GraphGrAPETrainer):
- """GRAFT backward + PairNorm forward (center & scale normalization)."""
+class GRAFTPairNorm(KAFTTrainer):
+ """KAFT backward + PairNorm forward (center & scale normalization)."""
def __init__(self, *args, pn_scale=1.0, **kwargs):
super().__init__(*args, **kwargs)
@@ -138,13 +138,13 @@ class GRAFTPairNorm(GraphGrAPETrainer):
# ═══════════════════════════════════════════════════════════════════════════
-# GRAFT + JKNet combo
+# KAFT + JKNet combo
# ═══════════════════════════════════════════════════════════════════════════
-class GRAFTJKNet(GraphGrAPETrainer):
- """GRAFT backward + JKNet forward (jumping knowledge max-pool).
+class GRAFTJKNet(KAFTTrainer):
+ """KAFT backward + JKNet forward (jumping knowledge max-pool).
Note: JKNet changes the output architecture. We max-pool hidden layers
- and project to num_classes. GRAFT backward operates on hidden layers
+ and project to num_classes. KAFT backward operates on hidden layers
as usual; the JK projection is treated as the output layer.
"""
@@ -187,7 +187,7 @@ class GRAFTJKNet(GraphGrAPETrainer):
def _update_weights(self, inter, E0, deltas):
"""Override to handle JK projection separately."""
- # Update hidden layers using GRAFT feedback as usual
+ # Update hidden layers using KAFT feedback as usual
X = self.data['X']
Hs = inter['Hs']
H0 = inter['H0']
@@ -265,7 +265,7 @@ def main():
per_seed_data = {}
# Reuse existing per-seed data from other experiments
- # BP, ResGCN, GRAFT from resgcn_20seeds
+ # BP, ResGCN, KAFT from resgcn_20seeds
try:
with open('results/resgcn_20seeds/per_seed_data.json') as f:
resgcn_cache = json.load(f)
@@ -292,11 +292,11 @@ def main():
'DropEdge': (BPDropEdgeTrainer, {'drop_rate': 0.5}),
'PairNorm': (BPPairNormTrainer, {'pn_scale': 1.0}),
'JKNet': (JKNetTrainer, {}),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
- 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra),
- 'GRAFT+DropEdge': (GRAFTDropEdge, {**grape_extra, 'drop_rate': 0.5}),
- 'GRAFT+PairNorm': (GRAFTPairNorm, {**grape_extra, 'pn_scale': 1.0}),
- 'GRAFT+JKNet': (GRAFTJKNet, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
+ 'KAFT+ResGCN': (GRAFTResGCN, grape_extra),
+ 'KAFT+DropEdge': (GRAFTDropEdge, {**grape_extra, 'drop_rate': 0.5}),
+ 'KAFT+PairNorm': (GRAFTPairNorm, {**grape_extra, 'pn_scale': 1.0}),
+ 'KAFT+JKNet': (GRAFTJKNet, grape_extra),
}
datasets_cfg = {
@@ -330,7 +330,7 @@ def main():
cached = resgcn_cache[f"{ds_name}_BP"].get(sk)
elif mname == 'ResGCN' and f"{ds_name}_ResGCN" in resgcn_cache:
cached = resgcn_cache[f"{ds_name}_ResGCN"].get(sk)
- elif mname == 'GRAFT' and f"{ds_name}_GRAFT" in resgcn_cache:
+ elif mname == 'KAFT' and f"{ds_name}_GRAFT" in resgcn_cache:
cached = resgcn_cache[f"{ds_name}_GRAFT"].get(sk)
elif mname == 'DropEdge':
de_key = f"{ds_name}_gcn_L6"
@@ -369,14 +369,14 @@ def main():
for ds in ['Cora', 'CiteSeer', 'DBLP']:
print(f"\n--- {ds} GCN L=6 lr=0.01 ---")
- print(f"{'Method':<18} {'Mean±Std':>12} {'vs GRAFT':>18} {'vs FwdTrick':>18}")
+ print(f"{'Method':<18} {'Mean±Std':>12} {'vs KAFT':>18} {'vs FwdTrick':>18}")
print("-" * 70)
- # Get GRAFT accs for comparison
+ # Get KAFT accs for comparison
gr_accs = np.array([per_seed_data[f"{ds}_GRAFT"][str(s)] for s in SEEDS]) * 100
for mname in ['BP', 'ResGCN', 'DropEdge', 'PairNorm', 'JKNet',
- 'GRAFT', 'GRAFT+ResGCN', 'GRAFT+DropEdge', 'GRAFT+PairNorm', 'GRAFT+JKNet']:
+ 'KAFT', 'KAFT+ResGCN', 'KAFT+DropEdge', 'KAFT+PairNorm', 'KAFT+JKNet']:
key = f"{ds}_{mname}"
if key not in per_seed_data or len(per_seed_data[key]) < 20:
print(f" {mname:<16} MISSING ({len(per_seed_data.get(key, {}))} seeds)")
@@ -387,8 +387,8 @@ def main():
results[key] = {'mean': float(m), 'std': float(s), 'accs': accs.tolist()}
- # Paired t-test vs GRAFT
- if mname != 'GRAFT':
+ # Paired t-test vs KAFT
+ if mname != 'KAFT':
t_stat, p_val = scipy_stats.ttest_rel(accs, gr_accs)
delta = m - gr_accs.mean()
sig = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else ('*' if p_val < 0.05 else 'ns'))
@@ -402,8 +402,8 @@ def main():
# Paired t-test vs forward trick only
fwd_map = {
- 'GRAFT+ResGCN': 'ResGCN', 'GRAFT+DropEdge': 'DropEdge',
- 'GRAFT+PairNorm': 'PairNorm', 'GRAFT+JKNet': 'JKNet'
+ 'KAFT+ResGCN': 'ResGCN', 'KAFT+DropEdge': 'DropEdge',
+ 'KAFT+PairNorm': 'PairNorm', 'KAFT+JKNet': 'JKNet'
}
if mname in fwd_map:
fwd_key = f"{ds}_{fwd_map[mname]}"
@@ -431,8 +431,8 @@ def main():
for ds in ['Cora', 'CiteSeer', 'DBLP']:
print(f"\n{ds}:")
gr_m = results.get(f"{ds}_GRAFT", {}).get('mean', 0)
- for combo, fwd in [('GRAFT+ResGCN', 'ResGCN'), ('GRAFT+DropEdge', 'DropEdge'),
- ('GRAFT+PairNorm', 'PairNorm'), ('GRAFT+JKNet', 'JKNet')]:
+ for combo, fwd in [('KAFT+ResGCN', 'ResGCN'), ('KAFT+DropEdge', 'DropEdge'),
+ ('KAFT+PairNorm', 'PairNorm'), ('KAFT+JKNet', 'JKNet')]:
ck = f"{ds}_{combo}"
fk = f"{ds}_{fwd}"
if ck in results and fk in results:
@@ -442,7 +442,7 @@ def main():
vs_fw = results[ck].get(f'vs_{fwd}', {})
better_than_both = c_m > gr_m and c_m > f_m
marker = "✓ ADDITIVE" if better_than_both else "✗ not additive"
- print(f" {combo}: {c_m:.1f} | GRAFT={gr_m:.1f} | {fwd}={f_m:.1f} → {marker}")
+ print(f" {combo}: {c_m:.1f} | KAFT={gr_m:.1f} | {fwd}={f_m:.1f} → {marker}")
# Save
with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f:
diff --git a/experiments/run_cora_perturb.py b/experiments/run_cora_perturb.py
index 1dabc95..1124b97 100644
--- a/experiments/run_cora_perturb.py
+++ b/experiments/run_cora_perturb.py
@@ -13,7 +13,7 @@ import numpy as np
import json
import os
from src.data import load_dataset, build_normalized_adj, build_row_normalized_adj
-from src.trainers import BPTrainer, DFATrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, DFATrainer, KAFTTrainer
device = 'cuda:0'
SEEDS = [0, 1, 2, 3, 4]
@@ -111,7 +111,7 @@ def main():
common = dict(data=data, hidden_dim=64, lr=0.01, weight_decay=5e-4,
num_layers=L, residual_alpha=0.0, backbone='gcn')
bp_accs.append(train_one(BPTrainer, common, {}, seed))
- gr_accs.append(train_one(GraphGrAPETrainer, common, grape_extra, seed))
+ gr_accs.append(train_one(KAFTTrainer, common, grape_extra, seed))
bp, gr = np.mean(bp_accs)*100, np.mean(gr_accs)*100
delta = gr - bp
diff --git a/experiments/run_cs_full.py b/experiments/run_cs_full.py
index d66fc59..d170e13 100644
--- a/experiments/run_cs_full.py
+++ b/experiments/run_cs_full.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""H19 CitationFull-CiteSeer (4.2K, deg 2.5, 6-class) — same regime as Planetoid CiteSeer.
-Quick BP + GRAFT depth sweep to confirm/extend the 'GRAFT wins on real sparse citation' story."""
+Quick BP + KAFT depth sweep to confirm/extend the 'KAFT wins on real sparse citation' story."""
import torch, sys, numpy as np, time
import torch.nn as nn, torch.nn.functional as F
@@ -9,7 +9,7 @@ from torch_geometric.nn import GCNConv
from torch_geometric.utils import add_self_loops, degree
sys.path.insert(0, '/home/yurenh2/graph-grape')
-from src.trainers import GraphGrAPETrainer
+from src.trainers import KAFTTrainer
DATA_ROOT = '/home/yurenh2/graph-grape/data/CFull'
device = torch.device('cuda:0')
@@ -90,7 +90,7 @@ def bp_one(L, seed, d, train_mask, val_mask, test_mask, epochs=200, lr=5e-3, hid
return bt
-def graft_one(L, seed, d, A_hat, A_row, A_row_T, train_mask, val_mask, test_mask,
+def kaft_one(L, seed, d, A_hat, A_row, A_row_T, train_mask, val_mask, test_mask,
epochs=200, lr=5e-3, hidden=128):
torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed)
data = {
@@ -99,7 +99,7 @@ def graft_one(L, seed, d, A_hat, A_row, A_row_T, train_mask, val_mask, test_mask
'num_features': d.x.shape[1], 'num_classes': int(d.y.max())+1,
'num_nodes': d.num_nodes, 'traces': {},
}
- trainer = GraphGrAPETrainer(
+ trainer = KAFTTrainer(
data=data, hidden_dim=hidden, lr=lr, weight_decay=0.0,
lr_feedback=0.5, num_probes=64, topo_mode='fixed_A', max_topo_power=3,
diffusion_alpha=0.5, diffusion_iters=10,
@@ -133,14 +133,14 @@ def main():
t0 = time.time()
bp = bp_one(L, s, d, tm, vm, tem)
t1 = time.time()
- gf = graft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem)
+ gf = kaft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem)
t2 = time.time()
bp_accs.append(bp); gf_accs.append(gf)
- print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) GRAFT={gf:.4f}({t2-t1:.0f}s)', flush=True)
+ print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) KAFT={gf:.4f}({t2-t1:.0f}s)', flush=True)
bp_m, bp_sd = np.mean(bp_accs), np.std(bp_accs)
gf_m, gf_sd = np.mean(gf_accs), np.std(gf_accs)
bp_res[L] = (bp_m, bp_sd); gf_res[L] = (gf_m, gf_sd)
- print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} GRAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True)
+ print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} KAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True)
if __name__ == '__main__':
diff --git a/experiments/run_dblp_depth.py b/experiments/run_dblp_depth.py
index d63b94a..f91440a 100644
--- a/experiments/run_dblp_depth.py
+++ b/experiments/run_dblp_depth.py
@@ -11,8 +11,7 @@ import os
import time
from torch_geometric.datasets import CitationFull
from src.data import build_normalized_adj, build_row_normalized_adj, spmm, precompute_traces
-from src.trainers import BPTrainer, DFATrainer, GraphGrAPETrainer
-from benchmark_efficient import GraphGrAPEEfficient
+from src.trainers import BPTrainer, DFATrainer, KAFTTrainer
device = 'cuda:0'
SEEDS = [0, 1, 2, 3, 4]
@@ -108,7 +107,7 @@ def main():
row = {}
for mname, cls, extra in [('BP', BPTrainer, {}),
('DFA', DFATrainer, dict(diffusion_alpha=0.5, diffusion_iters=10)),
- ('GrAPE', GraphGrAPETrainer, grape_extra)]:
+ ('GrAPE', KAFTTrainer, grape_extra)]:
accs = [train_one(cls, common, extra, s) for s in SEEDS]
row[mname] = {'mean': float(np.mean(accs)), 'std': float(np.std(accs))}
results[key] = row
@@ -123,7 +122,6 @@ def main():
common = dict(data=dblp, hidden_dim=64, lr=0.01, weight_decay=5e-4,
num_layers=L, residual_alpha=0.0, backbone=bb)
bp_ms = time_method(BPTrainer, common, {})
- eff_ms = time_method(GraphGrAPEEfficient, common,
dict(lr_feedback=0.5, num_probes=64, max_topo_power=3,
diff_alpha=0.5, align_every=10))
key = f"DBLP_eff|{bb}|L={L}"
@@ -146,7 +144,7 @@ def main():
key = f"{ds_name}|{bb}|L={L}|lr=0.01"
row = {}
for mname, cls, extra in [('BP', BPTrainer, {}),
- ('GrAPE', GraphGrAPETrainer, grape_extra)]:
+ ('GrAPE', KAFTTrainer, grape_extra)]:
accs = [train_one(cls, common, extra, s) for s in SEEDS]
row[mname] = {'mean': float(np.mean(accs)), 'std': float(np.std(accs))}
results[key] = row
diff --git a/experiments/run_dblp_depth_scaling.py b/experiments/run_dblp_depth_scaling.py
index 4c0bc11..86a5af1 100644
--- a/experiments/run_dblp_depth_scaling.py
+++ b/experiments/run_dblp_depth_scaling.py
@@ -1,16 +1,16 @@
#!/usr/bin/env python3
"""E1: DBLP depth scaling — upgrade depth_stress 3-seed to 20 seeds on DBLP,
extend to L={8,12,16,20,24,32}. Goal: confirm (or falsify) the preliminary
-finding that GRAFT > ResGCN at L=16 (3-seed: 69.9 vs 63.7) and scales to L=32.
+finding that KAFT > ResGCN at L=16 (3-seed: 69.9 vs 63.7) and scales to L=32.
-BP vs ResGCN vs GRAFT vs GRAFT+ResGCN, GCN backbone, lr=0.01, 200 epochs."""
+BP vs ResGCN vs KAFT vs KAFT+ResGCN, GCN backbone, lr=0.01, 200 epochs."""
import torch
import numpy as np
import json
import os
from scipy import stats as scipy_stats
-from src.trainers import BPTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, KAFTTrainer
from run_deep_baselines import ResGCNTrainer
from run_combo_20seeds import GRAFTResGCN
from run_dblp_depth import load_dblp
@@ -27,8 +27,8 @@ grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10,
METHODS = {
'BP': (BPTrainer, {}),
'ResGCN': (ResGCNTrainer, {}),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
- 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
+ 'KAFT+ResGCN': (GRAFTResGCN, grape_extra),
}
@@ -100,11 +100,11 @@ def main():
'per_seed': vals.tolist()}
print(f" {mname:<15} {vals.mean():5.1f} ± {vals.std():4.1f}")
- # GRAFT vs ResGCN (paired)
+ # KAFT vs ResGCN (paired)
g_accs = np.array([per_seed_data[f"DBLP_L{L}_GRAFT"][str(s)] for s in SEEDS]) * 100
r_accs = np.array([per_seed_data[f"DBLP_L{L}_ResGCN"][str(s)] for s in SEEDS]) * 100
t_gr, p_gr = scipy_stats.ttest_rel(g_accs, r_accs)
- print(f" GRAFT vs ResGCN: Δ={g_accs.mean() - r_accs.mean():+.1f}, p={p_gr:.4f}")
+ print(f" KAFT vs ResGCN: Δ={g_accs.mean() - r_accs.mean():+.1f}, p={p_gr:.4f}")
with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f:
json.dump(results, f, indent=2)
diff --git a/experiments/run_depth_extras.py b/experiments/run_depth_extras.py
index 66a7d45..e3b860b 100644
--- a/experiments/run_depth_extras.py
+++ b/experiments/run_depth_extras.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""H11: Fill depth sweep at L=14 and L=18 to densify Fig 4(a).
-3 methods (BP / DFA-GNN / GRAFT) × 4 datasets × 2 depths × 20 seeds = 480 runs.
+3 methods (BP / DFA-GNN / KAFT) × 4 datasets × 2 depths × 20 seeds = 480 runs.
"""
import torch
@@ -8,7 +8,7 @@ import numpy as np
import json
import os
from src.data import load_dataset
-from src.trainers import BPTrainer, DFAGNNTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, DFAGNNTrainer, KAFTTrainer
from run_dblp_depth import load_dblp
device = 'cuda:0'
@@ -24,7 +24,7 @@ dfagnn_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, max_topo_power=3)
METHODS = {
'BP': (BPTrainer, {}),
'DFA-GNN': (DFAGNNTrainer, dfagnn_extra),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
}
diff --git a/experiments/run_dfagnn_depth.py b/experiments/run_dfagnn_depth.py
index ed6e6c3..1a36485 100644
--- a/experiments/run_dfagnn_depth.py
+++ b/experiments/run_dfagnn_depth.py
@@ -2,9 +2,9 @@
"""H7: DFA-GNN depth sweep for Figure 4(a)-style plot.
Runs DFA-GNN at L ∈ {4, 8, 10, 12, 16, 20} × {Cora, CiteSeer, PubMed, DBLP} × 20 seeds.
-L=6 data already exists from prior experiments; L=2/3 skipped (CiteSeer L=2 GRAFT soft spot).
+L=6 data already exists from prior experiments; L=2/3 skipped (CiteSeer L=2 KAFT soft spot).
-Combined with existing BP and GRAFT depth data, produces 3-method depth curves for Figure 4(a).
+Combined with existing BP and KAFT depth data, produces 3-method depth curves for Figure 4(a).
"""
import torch
diff --git a/experiments/run_grad_reach_20seeds.py b/experiments/run_grad_reach_20seeds.py
index b5ad53b..b9ac8b9 100644
--- a/experiments/run_grad_reach_20seeds.py
+++ b/experiments/run_grad_reach_20seeds.py
@@ -12,7 +12,7 @@ import json
import os
from scipy import stats as scipy_stats
from src.data import load_dataset, spmm
-from src.trainers import BPTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, KAFTTrainer
device = 'cuda:0'
ALL_SEEDS = list(range(20))
@@ -31,7 +31,7 @@ def measure_one(data, L, backbone, seed):
torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed)
bp = BPTrainer(**common)
torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed)
- gr = GraphGrAPETrainer(**common, **grape_extra)
+ gr = KAFTTrainer(**common, **grape_extra)
gr.align_mode = 'chain_norm'
for _ in range(EPOCHS):
@@ -46,10 +46,10 @@ def measure_one(data, L, backbone, seed):
loss.backward()
bp_norms = [bp.weights[l].grad.norm().item() for l in range(L)]
- # GRAFT feedback norms
+ # KAFT feedback norms
Z_gr, inter = gr.forward()
E0, E_bar = gr._output_error(Z_gr)
- graft_norms = []
+ kaft_norms = []
for l in range(L - 1):
power = min(L - l, gr.max_topo_power)
topo_E = E_bar
@@ -57,13 +57,13 @@ def measure_one(data, L, backbone, seed):
topo_E = spmm(A, topo_E)
fb = topo_E @ gr.Rs[l]
relu_gate = (inter['Zs'][l].detach() > 0).float()
- graft_norms.append((relu_gate * fb).norm().item())
+ kaft_norms.append((relu_gate * fb).norm().item())
bp_acc = bp.evaluate('test_mask')
gr_acc = gr.evaluate('test_mask')
del bp, gr; torch.cuda.empty_cache()
- return bp_norms, graft_norms, bp_acc, gr_acc
+ return bp_norms, kaft_norms, bp_acc, gr_acc
def main():
@@ -101,10 +101,10 @@ def main():
bn, gn, ba, ga = measure_one(data, L, backbone, seed)
per_seed_data[key][seed_key] = {
- 'bp_norms': bn, 'graft_norms': gn,
+ 'bp_norms': bn, 'kaft_norms': gn,
'bp_acc': ba, 'gr_acc': ga
}
- print(f" seed {seed}: BP {ba*100:.1f}% GRAFT {ga*100:.1f}%", flush=True)
+ print(f" seed {seed}: BP {ba*100:.1f}% KAFT {ga*100:.1f}%", flush=True)
# Save incrementally
with open(old_per_seed_file, 'w') as f:
@@ -121,7 +121,7 @@ def main():
t_stat, p_val = scipy_stats.ttest_rel(gr_accs, bp_accs)
avg_bp_norms = np.mean([sd[str(s)]['bp_norms'] for s in ALL_SEEDS], axis=0)
- avg_gr_norms = np.mean([sd[str(s)]['graft_norms'] for s in ALL_SEEDS], axis=0)
+ avg_gr_norms = np.mean([sd[str(s)]['kaft_norms'] for s in ALL_SEEDS], axis=0)
results[key] = {
'bp_acc_mean': float(bp_accs.mean()),
@@ -142,10 +142,10 @@ def main():
sig = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else ('*' if p_val < 0.05 else 'ns'))
print(f"\n {key}:")
print(f" BP: {bp_accs.mean():.1f} ± {bp_accs.std():.1f}%")
- print(f" GRAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}%")
+ print(f" KAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}%")
print(f" Δ: {(gr_accs-bp_accs).mean():+.1f} ± {(gr_accs-bp_accs).std():.1f}% t={t_stat:.2f} p={p_val:.4f} {sig}")
print(f" BP norm L0: {avg_bp_norms[0]:.6f}")
- print(f" GRAFT norm L0: {avg_gr_norms[0]:.4f}")
+ print(f" KAFT norm L0: {avg_gr_norms[0]:.4f}")
with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f:
json.dump(results, f, indent=2)
diff --git a/experiments/run_hero_extras.py b/experiments/run_hero_extras.py
index c6fed4d..ced76b8 100644
--- a/experiments/run_hero_extras.py
+++ b/experiments/run_hero_extras.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""E0a+E0c+E0e: hero-table coverage expansion. Adds 3 datasets (PubMed stack,
-Coauthor-Physics, Coauthor-CS) × 5 methods (BP, DFA, DFA-GNN, GRAFT,
-GRAFT+ResGCN) × 20 seeds, all GCN L=6. Goal: 6-row hero table of homophilous
-citation/coauthor graphs where GRAFT or GRAFT+ResGCN is best per row."""
+Coauthor-Physics, Coauthor-CS) × 5 methods (BP, DFA, DFA-GNN, KAFT,
+KAFT+ResGCN) × 20 seeds, all GCN L=6. Goal: 6-row hero table of homophilous
+citation/coauthor graphs where KAFT or KAFT+ResGCN is best per row."""
import torch
import numpy as np
@@ -10,7 +10,7 @@ import json
import os
from scipy import stats as scipy_stats
from src.data import load_dataset, spmm
-from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, KAFTTrainer
from run_deep_baselines import ResGCNTrainer
from run_combo_20seeds import GRAFTResGCN
from run_large_graph_scout import load_and_check
@@ -54,8 +54,8 @@ METHODS = {
'BP': (BPTrainer, {}),
'DFA': (DFATrainer, dfagnn_extra),
'DFA-GNN': (DFAGNNTrainer, dfagnn_extra),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
- 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
+ 'KAFT+ResGCN': (GRAFTResGCN, grape_extra),
}
diff --git a/experiments/run_realworld_hero_L20.py b/experiments/run_realworld_hero_L20.py
index 93a6c91..3b352f1 100644
--- a/experiments/run_realworld_hero_L20.py
+++ b/experiments/run_realworld_hero_L20.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""H33: 20-seed extension of L=20 hero on 4 real-world datasets × {BP, DFA, DFA-GNN, GRAFT}.
+"""H33: 20-seed extension of L=20 hero on 4 real-world datasets × {BP, DFA, DFA-GNN, KAFT}.
Paper setup (5%/class, hidden=64, lr=0.01, no scheduler, 200 epochs, GCN backbone, no dropout/BN/res).
Tightens DBLP std (0.121 at 10-seed bimodal) for paper-grade stats.
@@ -17,7 +17,7 @@ from torch_geometric.nn import GCNConv
from torch_geometric.utils import add_self_loops, degree
sys.path.insert(0, '/home/yurenh2/graph-grape')
-from src.trainers import GraphGrAPETrainer
+from src.trainers import KAFTTrainer
device = torch.device('cuda:2')
@@ -92,7 +92,7 @@ def bp_one(L, seed, d, tm, vm, tem, epochs=200, lr=0.01, hidden=64):
return bt
-def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem,
+def kaft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem,
epochs=200, lr=0.01, hidden=64):
torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed)
data = {
@@ -101,7 +101,7 @@ def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem,
'num_features': d.x.shape[1], 'num_classes': int(d.y.max())+1,
'num_nodes': d.num_nodes, 'traces': {},
}
- trainer = GraphGrAPETrainer(
+ trainer = KAFTTrainer(
data=data, hidden_dim=hidden, lr=lr, weight_decay=5e-4,
lr_feedback=0.5, num_probes=64, topo_mode='fixed_A', max_topo_power=3,
diffusion_alpha=0.5, diffusion_iters=10,
@@ -149,21 +149,21 @@ def main():
t0 = time.time()
bp = bp_one(L, s, d, tm, vm, tem)
t1 = time.time()
- gf = graft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem)
+ gf = kaft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem)
t2 = time.time()
bp_a.append(bp); gf_a.append(gf)
- print(f' s={s} L={L}: BP={bp:.4f}({t1-t0:.0f}s) GRAFT={gf:.4f}({t2-t1:.0f}s)', flush=True)
+ print(f' s={s} L={L}: BP={bp:.4f}({t1-t0:.0f}s) KAFT={gf:.4f}({t2-t1:.0f}s)', flush=True)
bp_m, bp_sd = float(np.mean(bp_a)), float(np.std(bp_a))
gf_m, gf_sd = float(np.mean(gf_a)), float(np.std(gf_a))
- out[name] = dict(seeds=seeds, BP=bp_a, GRAFT=gf_a, BP_mean=bp_m, BP_std=bp_sd,
+ out[name] = dict(seeds=seeds, BP=bp_a, KAFT=gf_a, BP_mean=bp_m, BP_std=bp_sd,
GRAFT_mean=gf_m, GRAFT_std=gf_sd)
- print(f' >>> {name} L=20 (seeds {s_lo}-{s_hi-1}): BP {bp_m:.4f}±{bp_sd:.4f} GRAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True)
+ print(f' >>> {name} L=20 (seeds {s_lo}-{s_hi-1}): BP {bp_m:.4f}±{bp_sd:.4f} KAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True)
del d, A_hat, A_row, A_row_T
torch.cuda.empty_cache()
print('\n=== SUMMARY (this run) ===', flush=True)
for k, v in out.items():
- print(f' {k}: BP {v["BP_mean"]:.4f}±{v["BP_std"]:.4f} GRAFT {v["GRAFT_mean"]:.4f}±{v["GRAFT_std"]:.4f}', flush=True)
+ print(f' {k}: BP {v["BP_mean"]:.4f}±{v["BP_std"]:.4f} KAFT {v["GRAFT_mean"]:.4f}±{v["GRAFT_std"]:.4f}', flush=True)
if __name__ == '__main__':
diff --git a/experiments/run_resgcn_20seeds.py b/experiments/run_resgcn_20seeds.py
index 995a568..b56bb04 100644
--- a/experiments/run_resgcn_20seeds.py
+++ b/experiments/run_resgcn_20seeds.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Task 7016bd94 Part 1: ResGCN vs GRAFT, 20 seeds, paired t-tests."""
+"""Task 7016bd94 Part 1: ResGCN vs KAFT, 20 seeds, paired t-tests."""
import torch
import numpy as np
@@ -7,7 +7,7 @@ import json
import os
from scipy import stats as scipy_stats
from src.data import load_dataset
-from src.trainers import BPTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, KAFTTrainer
from run_deep_baselines import ResGCNTrainer
from run_dblp_depth import load_dblp
@@ -48,7 +48,7 @@ def main():
METHODS = {
'BP': (BPTrainer, {}),
'ResGCN': (ResGCNTrainer, {}),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
}
datasets_cfg = {
@@ -92,9 +92,9 @@ def main():
del data; torch.cuda.empty_cache()
- # Paired t-tests: GRAFT vs ResGCN
+ # Paired t-tests: KAFT vs ResGCN
print("\n" + "=" * 70)
- print("Paired t-tests: GRAFT vs ResGCN (20 seeds)")
+ print("Paired t-tests: KAFT vs ResGCN (20 seeds)")
print("-" * 70)
for ds in ['Cora', 'CiteSeer', 'DBLP']:
@@ -102,7 +102,7 @@ def main():
res_accs = np.array(results[f"{ds}_ResGCN"]['accs'])
gr_accs = np.array(results[f"{ds}_GRAFT"]['accs'])
- # GRAFT vs ResGCN
+ # KAFT vs ResGCN
t_stat, p_val = scipy_stats.ttest_rel(gr_accs, res_accs)
delta = gr_accs.mean() - res_accs.mean()
sig = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else ('*' if p_val < 0.05 else 'ns'))
@@ -112,7 +112,7 @@ def main():
'p_value': float(p_val), 'significant': bool(p_val < 0.05),
}
- # GRAFT vs BP
+ # KAFT vs BP
t2, p2 = scipy_stats.ttest_rel(gr_accs, bp_accs)
d2 = gr_accs.mean() - bp_accs.mean()
sig2 = '***' if p2 < 0.001 else ('**' if p2 < 0.01 else ('*' if p2 < 0.05 else 'ns'))
@@ -134,9 +134,9 @@ def main():
print(f"\n{ds}:")
print(f" BP: {bp_accs.mean():.1f} ± {bp_accs.std():.1f}")
print(f" ResGCN: {res_accs.mean():.1f} ± {res_accs.std():.1f}")
- print(f" GRAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}")
- print(f" GRAFT vs ResGCN: Δ{delta:+.1f}% p={p_val:.6f} {sig}")
- print(f" GRAFT vs BP: Δ{d2:+.1f}% p={p2:.6f} {sig2}")
+ print(f" KAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}")
+ print(f" KAFT vs ResGCN: Δ{delta:+.1f}% p={p_val:.6f} {sig}")
+ print(f" KAFT vs BP: Δ{d2:+.1f}% p={p2:.6f} {sig2}")
with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f:
json.dump(results, f, indent=2)
diff --git a/experiments/run_shallow_depth.py b/experiments/run_shallow_depth.py
index 68c9138..5f1cc26 100644
--- a/experiments/run_shallow_depth.py
+++ b/experiments/run_shallow_depth.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""E2: Shallow depth (L=2,3,4) on 4 datasets. Last exploratory avenue after
-E1 (deep scaling) and E0-extras (more datasets) both failed to extend GRAFT's
-regime. If GRAFT still wins at L=2/3 (standard GNN depth), we can counter
-the reviewer attack 'L=5,6 nobody uses'. If GRAFT matches BP only at L=5,6,
+E1 (deep scaling) and E0-extras (more datasets) both failed to extend KAFT's
+regime. If KAFT still wins at L=2/3 (standard GNN depth), we can counter
+the reviewer attack 'L=5,6 nobody uses'. If KAFT matches BP only at L=5,6,
paper stays at current scope and we ship."""
import torch
@@ -11,7 +11,7 @@ import json
import os
from scipy import stats as scipy_stats
from src.data import load_dataset
-from src.trainers import BPTrainer, GraphGrAPETrainer
+from src.trainers import BPTrainer, KAFTTrainer
from run_deep_baselines import ResGCNTrainer
from run_combo_20seeds import GRAFTResGCN
from run_dblp_depth import load_dblp
@@ -27,8 +27,8 @@ grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10,
METHODS = {
'BP': (BPTrainer, {}),
- 'GRAFT': (GraphGrAPETrainer, grape_extra),
- 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra),
+ 'KAFT': (KAFTTrainer, grape_extra),
+ 'KAFT+ResGCN': (GRAFTResGCN, grape_extra),
}
@@ -108,10 +108,10 @@ def main():
t, p = scipy_stats.ttest_rel(gr_accs, bp_accs)
delta = gr_accs.mean() - bp_accs.mean()
print(f" {ds} L={L}: BP {bp_accs.mean():5.1f}±{bp_accs.std():4.1f} "
- f"GRAFT {gr_accs.mean():5.1f}±{gr_accs.std():4.1f} "
- f"GRAFT+ResGCN {stk_accs.mean():5.1f}±{stk_accs.std():4.1f} "
- f"Δ(GRAFT-BP)={delta:+.1f}, p={p:.4f}")
- for mname, accs in [('BP', bp_accs), ('GRAFT', gr_accs), ('GRAFT+ResGCN', stk_accs)]:
+ f"KAFT {gr_accs.mean():5.1f}±{gr_accs.std():4.1f} "
+ f"KAFT+ResGCN {stk_accs.mean():5.1f}±{stk_accs.std():4.1f} "
+ f"Δ(KAFT-BP)={delta:+.1f}, p={p:.4f}")
+ for mname, accs in [('BP', bp_accs), ('KAFT', gr_accs), ('KAFT+ResGCN', stk_accs)]:
key = f"{ds}_L{L}_{mname}"
results[key] = {'mean': float(accs.mean()), 'std': float(accs.std()),
'per_seed': accs.tolist()}
diff --git a/experiments/run_wikics_paper_setup.py b/experiments/run_wikics_paper_setup.py
index a2bf879..c9525e6 100644
--- a/experiments/run_wikics_paper_setup.py
+++ b/experiments/run_wikics_paper_setup.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""H15 WikiCS paper-setup depth sweep — Wikipedia academic articles.
~11.7K nodes, avg deg ~4.1, 10-class, undirected. Sparse + few-class
-fits GRAFT's regime profile. Test BP vs GRAFT at L ∈ {3,5,10,14,20} × 5 seeds.
+fits KAFT's regime profile. Test BP vs KAFT at L ∈ {3,5,10,14,20} × 5 seeds.
"""
import sys, time
import numpy as np
@@ -13,7 +13,7 @@ from torch_geometric.nn import GCNConv
from torch_geometric.utils import add_self_loops, degree, to_undirected
sys.path.insert(0, '/home/yurenh2/graph-grape')
-from src.trainers import GraphGrAPETrainer
+from src.trainers import KAFTTrainer
device = torch.device('cuda:0') # CUDA_VISIBLE_DEVICES=2 maps cuda:0 → physical GPU 2
@@ -88,7 +88,7 @@ def bp_one(L, seed, d, tm, vm, tem, epochs=200, lr=0.01, hidden=64):
return bt
-def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem,
+def kaft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem,
epochs=200, lr=0.01, hidden=64):
torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed)
data = {
@@ -97,7 +97,7 @@ def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem,
'num_features': d.x.shape[1], 'num_classes': int(d.y.max())+1,
'num_nodes': d.num_nodes, 'traces': {},
}
- trainer = GraphGrAPETrainer(
+ trainer = KAFTTrainer(
data=data, hidden_dim=hidden, lr=lr, weight_decay=5e-4,
lr_feedback=0.5, num_probes=64, topo_mode='fixed_A', max_topo_power=3,
diffusion_alpha=0.5, diffusion_iters=10,
@@ -133,13 +133,13 @@ def main():
t0 = time.time()
bp = bp_one(L, s, d, tm, vm, tem)
t1 = time.time()
- gf = graft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem)
+ gf = kaft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem)
t2 = time.time()
bp_a.append(bp); gf_a.append(gf)
- print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) GRAFT={gf:.4f}({t2-t1:.0f}s)', flush=True)
+ print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) KAFT={gf:.4f}({t2-t1:.0f}s)', flush=True)
bp_m, bp_sd = np.mean(bp_a), np.std(bp_a)
gf_m, gf_sd = np.mean(gf_a), np.std(gf_a)
- print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} GRAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True)
+ print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} KAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True)
if __name__ == '__main__':