Thibaut Forest Claude Fable 5 commited on
Commit
97af9ac
·
1 Parent(s): 34c30a6

Phase 62 postscript: scaling verdict is geography-insensitive (cluster-local and map-faithful travel variants <2% shift, arm ranking unchanged)

Browse files
agent_reports_bgkf/artifacts/travel_cluster.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Cluster-realistic travel for the synthetic fleets: a clone building is a
2
+ NEW building in its base's cluster - one nearest-neighbour-scale hop from
3
+ its base and its sibling clones (jittered, capped at local scale for the
4
+ remote site), and its base's real distance (small jitter) to everyone else.
5
+ Rewrites the travel frame inside fleet_k{K}.pkl -> fleetc_k{K}.pkl."""
6
+ import sys, pickle
7
+ from pathlib import Path
8
+ import numpy as np, pandas as pd
9
+
10
+ sys.path.insert(0, '/Users/THIF/repos/BatterySwapAI2026-Example')
11
+ from batteryswap_public.utils import load_dataset
12
+
13
+ K = int(sys.argv[1])
14
+ rng = np.random.default_rng(42 + K)
15
+ *_, sc = load_dataset(Path('/Users/THIF/repos/BatterySwapAI2026-Example/dataset/train'))
16
+ tc = sc[0]['travel_costs']
17
+ base_h = tc.set_index(['from', 'to'])['hours']
18
+ off = tc[tc['from'] != tc['to']]
19
+ nn = off.pivot(index='from', columns='to', values='hours').min(axis=1)
20
+ local = nn.clip(upper=2 * nn.median()) # remote-site guard
21
+
22
+ with open(f'/tmp/c5/fleet_k{K}.pkl', 'rb') as f:
23
+ FL = pickle.load(f)
24
+ locs = FL['locations']
25
+ base_of = locs.drop_duplicates('building').set_index('building')['base_building']
26
+ blds = sorted(locs['building'].unique())
27
+
28
+ rows, seen = [], {}
29
+ for i in blds:
30
+ for j in blds:
31
+ if i == j:
32
+ h = 0.0
33
+ else:
34
+ key = tuple(sorted((i, j)))
35
+ if key not in seen:
36
+ bi, bj = base_of[i], base_of[j]
37
+ if bi == bj:
38
+ h = float(local[bi]) * rng.uniform(0.6, 1.4)
39
+ else:
40
+ h = float(base_h.get((bi, bj), off['hours'].median()))
41
+ if i != bi or j != bj: # at least one clone: jitter
42
+ h *= rng.uniform(0.9, 1.1)
43
+ seen[key] = max(h, 0.02)
44
+ h = seen[key]
45
+ rows.append({'from': i, 'to': j, 'hours': h})
46
+ FL['travel'] = pd.DataFrame(rows)
47
+ with open(f'/tmp/c5/fleetc_k{K}.pkl', 'wb') as f:
48
+ pickle.dump(FL, f)
49
+ print(f'k={K}: cluster travel written ({len(blds)} buildings)')
agent_reports_bgkf/artifacts/travel_map.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Map-faithful travel for synthetic fleets (per the competition map:
2
+ one dominant metro cluster + small satellite clumps, hours apart).
3
+
4
+ Clusters inferred from the real travel matrix (average-linkage, 1h cut:
5
+ sizes [18,2,1,1,1,1]). Each CLONE building is assigned to a cluster with
6
+ probability proportional to real cluster size; its distances are sampled
7
+ from the empirical intra-cluster hop distribution (satellite clumps: the
8
+ map shows tight multi-dot sites -> local hops ~0.05-0.15h) and the real
9
+ between-cluster block distances. Fleet/telemetry unchanged.
10
+ fleet_k{K}.pkl -> fleetm_k{K}.pkl
11
+ """
12
+ import sys, pickle
13
+ from pathlib import Path
14
+ import numpy as np, pandas as pd
15
+ from scipy.cluster.hierarchy import linkage, fcluster
16
+ from scipy.spatial.distance import squareform
17
+
18
+ sys.path.insert(0, '/Users/THIF/repos/BatterySwapAI2026-Example')
19
+ from batteryswap_public.utils import load_dataset
20
+
21
+ K = int(sys.argv[1])
22
+ rng = np.random.default_rng(1000 + K)
23
+ *_, sc = load_dataset(Path('/Users/THIF/repos/BatterySwapAI2026-Example/dataset/train'))
24
+ tc = sc[0]['travel_costs']
25
+ piv = tc.pivot(index='from', columns='to', values='hours')
26
+ order = sorted(piv.index)
27
+ piv = piv.reindex(index=order, columns=order)
28
+ D = (piv.values + piv.values.T) / 2
29
+ np.fill_diagonal(D, 0)
30
+ lab = fcluster(linkage(squareform(D, checks=False), method='average'),
31
+ t=1.0, criterion='distance')
32
+ clus = pd.Series(lab, index=order)
33
+ sizes = clus.value_counts()
34
+
35
+ def intra_pool(c):
36
+ m = clus[clus == c].index
37
+ if len(m) < 2:
38
+ return np.array([0.08]) # tight clump scale (map dots)
39
+ ix = [order.index(b) for b in m]
40
+ v = D[np.ix_(ix, ix)][np.triu_indices(len(ix), 1)]
41
+ return v[v > 0] if (v > 0).any() else np.array([0.08])
42
+
43
+ def between_pool(c1, c2):
44
+ m1 = [order.index(b) for b in clus[clus == c1].index]
45
+ m2 = [order.index(b) for b in clus[clus == c2].index]
46
+ return D[np.ix_(m1, m2)].ravel()
47
+
48
+ INTRA = {c: intra_pool(c) for c in sizes.index}
49
+ BETW = {(a, b): between_pool(a, b) for a in sizes.index for b in sizes.index
50
+ if a != b}
51
+
52
+ with open(f'/tmp/c5/fleet_k{K}.pkl', 'rb') as f:
53
+ FL = pickle.load(f)
54
+ locs = FL['locations']
55
+ base_of = locs.drop_duplicates('building').set_index('building')['base_building']
56
+ blds = sorted(locs['building'].unique())
57
+
58
+ # cluster of every synthetic building: originals keep theirs; clones drawn
59
+ # proportional to real cluster sizes (metro gets ~75% of new buildings)
60
+ p = (sizes / sizes.sum()).sort_index()
61
+ assign = {}
62
+ for b in blds:
63
+ if b in clus.index:
64
+ assign[b] = int(clus[b])
65
+ else:
66
+ assign[b] = int(rng.choice(p.index.to_numpy(), p=p.to_numpy()))
67
+
68
+ rows, seen = [], {}
69
+ for i in blds:
70
+ for j in blds:
71
+ if i == j:
72
+ h = 0.0
73
+ else:
74
+ key = tuple(sorted((i, j)))
75
+ if key not in seen:
76
+ ci, cj = assign[i], assign[j]
77
+ both_real = i in clus.index and j in clus.index
78
+ if both_real:
79
+ h = float(D[order.index(i), order.index(j)])
80
+ elif ci == cj:
81
+ h = float(rng.choice(INTRA[ci])) * rng.uniform(0.7, 1.3)
82
+ else:
83
+ h = float(rng.choice(BETW[(ci, cj)])) * rng.uniform(0.9, 1.1)
84
+ seen[key] = max(h, 0.02)
85
+ h = seen[key]
86
+ rows.append({'from': i, 'to': j, 'hours': h})
87
+ FL['travel'] = pd.DataFrame(rows)
88
+ with open(f'/tmp/c5/fleetm_k{K}.pkl', 'wb') as f:
89
+ pickle.dump(FL, f)
90
+ n_new = sum(1 for b in blds if b not in clus.index)
91
+ metro = sizes.idxmax()
92
+ share = sum(1 for b in blds if assign[b] == metro) / len(blds)
93
+ print(f'k={K}: {len(blds)} buildings ({n_new} new), metro share {share:.2f}')
agent_reports_bgkf/c3_findings.md CHANGED
@@ -208,3 +208,20 @@ FINDINGS:
208
 
209
  Artifacts: /tmp/c5/ (generator synth.py, runner scale_run.py, fleets,
210
  per-arm CSVs, /tmp/cv_bgkfcand_ft2fix.csv).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
  Artifacts: /tmp/c5/ (generator synth.py, runner scale_run.py, fleets,
210
  per-arm CSVs, /tmp/cv_bgkfcand_ft2fix.csv).
211
+
212
+ ## Phase 62 postscript: geography robustness (user's map)
213
+
214
+ The real travel matrix clusters exactly like the competition map (average
215
+ linkage, 1h cut): one 18-building metro + one pair + four satellite
216
+ singletons; intra-metro hops median 0.25h, nearest-neighbour hops ~0.08h,
217
+ cross-cluster up to 10h. Two additional synthetic geographies were run at
218
+ k=2/4 (fleets unchanged, travel rebuilt): CLUSTER-LOCAL (clones one
219
+ nearest-neighbour-scale hop from their base) and MAP-FAITHFUL (clones
220
+ assigned to clusters proportional to real cluster sizes - metro share
221
+ .75/.78 - with intra/inter distances sampled from the empirical
222
+ distributions). Per-device costs moved by <2% in every cell and the arm
223
+ ranking never changed (champ ~= ft2 at 2x, mild ft2 inversion at 4x,
224
+ alpha 1.0 best everywhere). Cost is dominated by late/early, not travel:
225
+ the scaling verdict is geography-insensitive. Stopped early per user (two
226
+ k=4 alpha arms unfinished - pattern already unambiguous). Artifacts:
227
+ /tmp/c5/travel_map.py, travel_cluster.py, scalem_*/scalec_* CSVs.