| """ |
| Does the Figure-6 finite-sample gap appear under ANY plausible reading of the |
| paper's simulation description? Sweeps |
| * generation T = 1,3,5,10 (the paper's x-axis runs to 10) |
| * the noise s.d. of the S equation (selection strength; the paper only says |
| noise variances are drawn from [1,4], so 0.05 = near-deterministic |
| selection is a strictly more favourable setting than the text implies) |
| * average degree 2 read as |E| = d (default) and as |E| = 2d |
| * PC and GES, both with the paper's hyper-parameters |
| Run: python3 exp6_variants.py |
| """ |
| import json, os, warnings, time |
| import numpy as np |
|
|
| warnings.filterwarnings('ignore') |
| os.environ.setdefault('OMP_NUM_THREADS', '1') |
| OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'outputs') |
| os.makedirs(OUT, exist_ok=True) |
| N = 5000 |
|
|
|
|
| def one(task): |
| d, T, snoise, deg, seed = task |
| from evosel import random_static_dag, simulate_evolution, clique_augmented, cpdag, SEL |
| from exp6_synthetic import _edges_from_cl, _score |
| from causallearn.search.ConstraintBased.PC import pc |
| from causallearn.search.ScoreBased.GES import ges |
| rng = np.random.default_rng(97 * seed + 7 * d + T) |
| G = random_static_dag(d, rng, avg_deg=deg) |
| true = set((i, j) for j in range(d) for i in G.pa[j] if i != SEL) |
| anS = G.ancestors([SEL]) - {SEL} |
| gp = clique_augmented(G, d) |
| dd, uu = cpdag(gp) |
| orc = _score(dd, uu, true, anS) |
| X, _ = simulate_evolution(G, d, T, N, rng, s_noise=snoise) |
| out = {'d': d, 'T': T, 's_noise': snoise, 'avg_deg': deg, 'seed': seed, |
| 'anS': len(anS), 'oracle_standard': orc['precision_standard'], |
| 'oracle_ours': orc['precision_ours']} |
| cg = pc(X, alpha=0.05, indep_test='fisherz', show_progress=False) |
| a, b = _edges_from_cl(cg.G, d) |
| s = _score(a, b, true, anS) |
| out['PC_standard'], out['PC_ours'] = s['precision_standard'], s['precision_ours'] |
| out['PC_n_oriented'], out['PC_n_adj'] = s['n_oriented'], s['n_adjacencies'] |
| rec = ges(X, score_func='local_score_BIC', maxP=None, |
| parameters={'lambda_value': 2.0}) |
| a, b = _edges_from_cl(rec['G'], d) |
| s = _score(a, b, true, anS) |
| out['GES_standard'], out['GES_ours'] = s['precision_standard'], s['precision_ours'] |
| out['GES_n_oriented'], out['GES_n_adj'] = s['n_oriented'], s['n_adjacencies'] |
| return out |
|
|
|
|
| if __name__ == '__main__': |
| import multiprocessing as mp |
| tasks = [] |
| for T in (1, 3, 5, 10): |
| for sn in (0.05, 1.0, 2.0): |
| for seed in range(20): |
| tasks.append((20, T, sn, 2.0, seed)) |
| for T in (1, 3, 5, 10): |
| for seed in range(20): |
| tasks.append((20, T, 1.0, 4.0, seed)) |
| t0 = time.time() |
| with mp.Pool(processes=min(mp.cpu_count(), 12)) as pool: |
| rows = pool.map(one, tasks, chunksize=1) |
| agg = {} |
| for r in rows: |
| k = 'T%d_snoise%s_deg%s' % (r['T'], r['s_noise'], r['avg_deg']) |
| agg.setdefault(k, []).append(r) |
| summ = {} |
| for k, v in agg.items(): |
| e = {'n': len(v)} |
| for f in ('oracle_standard', 'oracle_ours', 'PC_standard', 'PC_ours', |
| 'GES_standard', 'GES_ours', 'PC_n_oriented', 'PC_n_adj', |
| 'GES_n_oriented', 'GES_n_adj'): |
| a = np.array([x[f] for x in v], dtype=float) |
| a = a[~np.isnan(a)] |
| e[f] = float(a.mean()) |
| e['PC_gap'] = e['PC_ours'] - e['PC_standard'] |
| e['GES_gap'] = e['GES_ours'] - e['GES_standard'] |
| e['PC_win_rate'] = float(np.mean([x['PC_ours'] > x['PC_standard'] for x in v])) |
| e['GES_win_rate'] = float(np.mean([x['GES_ours'] > x['GES_standard'] for x in v])) |
| summ[k] = e |
| json.dump({'summary': summ, 'runs': rows, 'runtime_sec': time.time() - t0}, |
| open(os.path.join(OUT, 'variants.json'), 'w'), indent=1) |
| for k in sorted(summ): |
| e = summ[k] |
| print('%-24s PC %.3f/%.3f gap %+.3f win %.2f | GES %.3f/%.3f gap %+.3f win %.2f' % |
| (k, e['PC_standard'], e['PC_ours'], e['PC_gap'], e['PC_win_rate'], |
| e['GES_standard'], e['GES_ours'], e['GES_gap'], e['GES_win_rate'])) |
|
|