import json, math, random from pathlib import Path import numpy as np SEED=1234 rng=np.random.default_rng(SEED) def cosine_dist(a,b): a=np.asarray(a,float); b=np.asarray(b,float) return 1-float(a@b)/(np.linalg.norm(a)*np.linalg.norm(b)+1e-12) def mutual_reachability(D,m=2): n=len(D); cores=np.zeros(n) for i in range(n): vals=np.sort(np.delete(D[i],i)); cores[i]=vals[min(m-1,len(vals)-1)] MR=np.maximum(D,np.maximum(cores[:,None],cores[None,:])); np.fill_diagonal(MR,0) return cores,MR def mst_cut_clusters(MR): # Kruskal MST, cut the largest unusually separated edge (density hierarchy toy). n=len(MR); edges=sorted((MR[i,j],i,j) for i in range(n) for j in range(i)) par=list(range(n)) def find(x): while par[x]!=x: par[x]=par[par[x]]; x=par[x] return x mst=[] for w,i,j in edges: a,b=find(i),find(j) if a!=b: par[a]=b; mst.append((w,i,j)) ws=np.array([x[0] for x in mst]) if len(ws)<2: return np.arange(n) gaps=np.diff(np.sort(ws)); k=int(np.argmax(gaps)) cut=float(np.sort(ws)[k+1]) # only split if gap is meaningful; this is the singleton/noise-safe hierarchy cut if cut <= (np.median(ws)+1e-12)*1.15: cut=float('inf') par=list(range(n)) for w,i,j in mst: if w