#!/usr/bin/env python3 import argparse, json, os, random import numpy as np import pandas as pd import importlib.util spec = importlib.util.spec_from_file_location('gcdm', './train_test_gcd_multidim.py') gcdm = importlib.util.module_from_spec(spec) spec.loader.exec_module(gcdm) def rel_from_truth(objects, perspectives): T = gcdm.multi_truth(objects, perspectives) return (T[:, None, :] <= T[None, :, :] + 1e-12).all(axis=2) def rel_from_points(F): return (F[:, None, :] <= F[None, :, :] + 1e-9).all(axis=2) def upper_atom(rel, idx): return rel[idx].copy() def heyting_imp(rel, U, V): bad = U & (~V) return ~(rel @ bad.astype(int) > 0) def eval_formula(node, rel, atoms): t = node['type'] if t == 'atom': return atoms[node['atom']] if t == 'and': return eval_formula(node['left'], rel, atoms) & eval_formula(node['right'], rel, atoms) if t == 'or': return eval_formula(node['left'], rel, atoms) | eval_formula(node['right'], rel, atoms) if t == 'imp': return heyting_imp(rel, eval_formula(node['left'], rel, atoms), eval_formula(node['right'], rel, atoms)) if t == 'not': return heyting_imp(rel, eval_formula(node['child'], rel, atoms), np.zeros(rel.shape[0], dtype=bool)) raise ValueError(t) def fstr(node, atom_names): t=node['type'] if t=='atom': return atom_names[node['atom']] if t=='not': return f"¬({fstr(node['child'], atom_names)})" if t=='and': return f"({fstr(node['left'], atom_names)} ∧ {fstr(node['right'], atom_names)})" if t=='or': return f"({fstr(node['left'], atom_names)} ∨ {fstr(node['right'], atom_names)})" if t=='imp': return f"({fstr(node['left'], atom_names)} → {fstr(node['right'], atom_names)})" raise ValueError(t) def rand_formula(rng, atom_ids, depth): if depth <= 0 or rng.random() < 0.35: return {'type': 'atom', 'atom': rng.choice(atom_ids)} t = rng.choice(['and', 'or', 'imp', 'not']) if t == 'not': return {'type': 'not', 'child': rand_formula(rng, atom_ids, depth - 1)} return {'type': t, 'left': rand_formula(rng, atom_ids, depth - 1), 'right': rand_formula(rng, atom_ids, depth - 1)} def main(): ap = argparse.ArgumentParser() ap.add_argument('--outdir', default='./gcd_formula_eval') ap.add_argument('--train-fracs', default='0.05,0.2,0.5') ap.add_argument('--lattices', default='A2,A3,E8,D24PLUS') ap.add_argument('--perspectives', default='2,3,5,7') ap.add_argument('--n-formulas', type=int, default=24) ap.add_argument('--max-depth', type=int, default=2) ap.add_argument('--seed', type=int, default=7) ap.add_argument('--formula-seed', type=int, default=17) ap.add_argument('--maxiter', type=int, default=20) args = ap.parse_args() os.makedirs(args.outdir, exist_ok=True) objects = gcdm.demo_objects_pm1_pm100() obj_to_idx = {a:i for i,a in enumerate(objects)} K_all = gcdm.build_demo_gram(objects) perspectives = [int(x) for x in args.perspectives.split(',') if x] gold_rel = rel_from_truth(objects, perspectives) anchor_objects = [-30,-15,-10,-6,-3,-2,2,3,5,6,10,15,30,42] atom_ids = [obj_to_idx[a] for a in anchor_objects] atom_names = {obj_to_idx[a]: f'p_{{{a}}}' for a in anchor_objects} atoms_gold = {aid: upper_atom(gold_rel, aid) for aid in atom_ids} frng = random.Random(args.formula_seed) formulas = [rand_formula(frng, atom_ids, args.max_depth) for _ in range(args.n_formulas)] details=[]; summary=[] for frac in [float(x) for x in args.train_fracs.split(',') if x]: rng = np.random.default_rng(args.seed) n = len(objects) perm = rng.permutation(n) n_train = max(4, int(round(frac*n))) train_idx = np.sort(perm[:n_train]); test_idx=np.sort(perm[n_train:]) train_objects=[objects[i] for i in train_idx]; test_objects=[objects[i] for i in test_idx] pos_edges = gcdm.build_order_edges(train_objects, perspectives) K_train = K_all[np.ix_(train_idx, train_idx)] K_test_train = K_all[np.ix_(test_idx, train_idx)] for lattice in [x.strip() for x in args.lattices.split(',') if x.strip()]: d = gcdm.lattice_basis(lattice)[1].shape[0] learn = gcdm.learn_chamber_coordinates(K_train, pos_edges, d=d, maxiter=args.maxiter) F_train = learn.F_train F_test = gcdm.center_cross_kernel(K_train, K_test_train) @ learn.A local_radius = 0 if lattice.upper().startswith('D24') else 1 q_train = gcdm.quantize_coords(F_train, lattice, local_radius=local_radius) q_test = gcdm.quantize_coords(F_test, lattice, local_radius=local_radius) Q_all = np.zeros((n,d), dtype=float) Q_all[train_idx] = q_train['quantized_points']; Q_all[test_idx] = q_test['quantized_points'] rel_q = rel_from_points(Q_all) atoms_q = {aid: upper_atom(rel_q, aid) for aid in atom_ids} ytrue=[]; ypred=[] for i, formula in enumerate(formulas): g = eval_formula(formula, gold_rel, atoms_gold)[test_idx].astype(int) p = eval_formula(formula, rel_q, atoms_q)[test_idx].astype(int) m = gcdm.binary_metrics(g,p) details.append({'train_frac': frac, 'lattice': lattice, 'formula_id': i, 'formula': fstr(formula, atom_names), **m}) ytrue.extend(g.tolist()); ypred.extend(p.tolist()) agg = gcdm.binary_metrics(np.array(ytrue), np.array(ypred)) summary.append({'train_frac': frac, 'lattice': lattice, 'n_formulas': args.n_formulas, 'train_n': len(train_idx), 'test_n': len(test_idx), 'test_n_unique_lattice_points': q_test['n_unique'], 'test_mean_quantization_error': float(np.mean(q_test['errors'])), **{f'micro_{k}':v for k,v in agg.items()}}) print(f'frac={frac} lattice={lattice} micro_mcc={agg["mcc"]:.4f}') pd.DataFrame(details).to_csv(os.path.join(args.outdir,'formula_metrics_detail.csv'), index=False) pd.DataFrame(summary).sort_values(['train_frac','micro_mcc'], ascending=[True,False]).to_csv(os.path.join(args.outdir,'formula_metrics_summary.csv'), index=False) json.dump({'perspectives': perspectives, 'anchor_objects': anchor_objects}, open(os.path.join(args.outdir,'config.json'),'w'), indent=2) if __name__=='__main__': main()