Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
test_real_cpp_rng.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3import argparse
4import subprocess
5import sys
6import os
7from pathlib import Path
8from typing import Tuple, Dict, List
9
10import numpy as np
11import pandas as pd
12import matplotlib.pyplot as plt
13
14
15
16def equicorr(n: int, rho: float) -> np.ndarray:
17 R = np.full((n, n), rho, dtype=float)
18 np.fill_diagonal(R, 1.0)
19 return R
20
21
22def load_matrix_txt(path: Path) -> np.ndarray:
23 txt = Path(path).read_text().strip().split()
24 it = iter(txt)
25 n = int(next(it))
26 vals = [float(next(it)) for _ in range(n * n)]
27 R = np.array(vals, dtype=float).reshape(n, n)
28 return R
29
30
31def matrix_to_stdin_payload(R: np.ndarray) -> str:
32 n = R.shape[0]
33 lines = [str(n)]
34 for i in range(n):
35 lines.append(" ".join(f"{float(x)}" for x in R[i]))
36 return "\n".join(lines) + "\n"
37
38
39
40def compile_cpp_if_needed(bin_path: Path, cpp_path: Path) -> None:
41 if bin_path.exists():
42 return
43 if not cpp_path or not cpp_path.exists():
44 raise FileNotFoundError(
45 f"Executable not found ({bin_path}) and C++ source not found ({cpp_path}). "
46 f"Passe --cpp <chemin/vers/correlated_rng.cpp> ou --bin <chemin/vers/binaire>."
47 )
48 print(f"[INFO] Compilation de {cpp_path} → {bin_path} ...")
49 cmd = ["g++", "-std=c++17", "-O2", "-Wall", str(cpp_path), "-o", str(bin_path)]
50 proc = subprocess.run(cmd, capture_output=True, text=True)
51 if proc.returncode != 0:
52 print(proc.stdout)
53 print(proc.stderr, file=sys.stderr)
54 raise RuntimeError("Échec de la compilation. Voir erreurs ci-dessus.")
55 print("[INFO] Compilation OK.")
56
57
58def run_cpp_once(bin_path: Path, R: np.ndarray, dist: str = "gaussian", seed: int = None) -> np.ndarray:
59 payload = matrix_to_stdin_payload(R)
60 args = [str(bin_path), dist]
61 if seed is not None:
62 args.append(str(int(seed)))
63
64 proc = subprocess.run(args, input=payload, capture_output=True, text=True)
65 if proc.returncode != 0:
66 raise RuntimeError(f"Exécution C++ a échoué (code {proc.returncode}): {proc.stderr.strip()}")
67
68 try:
69 vals = [float(tok) for tok in proc.stdout.strip().split()]
70 except ValueError:
71 raise RuntimeError(f"Sortie C++ non numérique:\n{proc.stdout}\nERR:\n{proc.stderr}")
72 return np.array(vals, dtype=float)
73
74
75def sample_many(bin_path: Path, R: np.ndarray, n_samples: int, seed_base: int, dist: str = "gaussian") -> np.ndarray:
76 """Appelle le binaire n_samples fois (seed = seed_base + i) et empile les réalisations."""
77 ys: List[np.ndarray] = []
78 for i in range(n_samples):
79 y = run_cpp_once(bin_path, R, dist=dist, seed=seed_base + i)
80 ys.append(y)
81 return np.vstack(ys)
82
83
84def compute_stats(Y: np.ndarray, R: np.ndarray) -> Dict[str, float]:
85 emp_corr = np.corrcoef(Y, rowvar=False)
86 err = emp_corr - R
87 means = Y.mean(axis=0)
88 stds = Y.std(axis=0, ddof=1)
89 return {
90 "samples": Y.shape[0],
91 "dim": Y.shape[1],
92 "max|corr_error|": float(np.max(np.abs(err))),
93 "mean|corr_error|": float(np.mean(np.abs(err))),
94 "max|mean|": float(np.max(np.abs(means))),
95 "max|std-1|": float(np.max(np.abs(stds - 1.0))),
96 }
97
98
99def plot_case(save_dir: Path, name: str, Y: np.ndarray, R: np.ndarray):
100 save_dir.mkdir(parents=True, exist_ok=True)
101 emp_corr = np.corrcoef(Y, rowvar=False)
102
103 plt.figure()
104 plt.imshow(emp_corr, vmin=-1, vmax=1)
105 plt.title(f"Empirical correlation – {name}")
106 plt.colorbar()
107 plt.tight_layout()
108 plt.savefig(save_dir / f"{name}_emp_corr.png", dpi=160)
109 plt.close()
110
111 plt.figure()
112 plt.imshow(R, vmin=-1, vmax=1)
113 plt.title(f"Target correlation – {name}")
114 plt.colorbar()
115 plt.tight_layout()
116 plt.savefig(save_dir / f"{name}_target_corr.png", dpi=160)
117 plt.close()
118
119 subset = Y[: min(4000, len(Y)), :]
120 plt.figure()
121 plt.scatter(subset[:, 0], subset[:, 1], s=5)
122 plt.title(f"Scatter y1 vs y2 – {name}")
123 plt.xlabel("y1")
124 plt.ylabel("y2")
125 plt.tight_layout()
126 plt.savefig(save_dir / f"{name}_scatter_y1_y2.png", dpi=160)
127 plt.close()
128
129 xs = np.linspace(Y[:, 0].min(), Y[:, 0].max(), 400)
130 pdf = 1.0 / np.sqrt(2 * np.pi) * np.exp(-0.5 * xs * xs)
131 plt.figure()
132 plt.hist(Y[:, 0], bins=60, density=True)
133 plt.plot(xs, pdf)
134 plt.title(f"Histogram(y1) + N(0,1) – {name}")
135 plt.xlabel("y1")
136 plt.ylabel("density")
137 plt.tight_layout()
138 plt.savefig(save_dir / f"{name}_hist_y1.png", dpi=160)
139 plt.close()
140
141
142
143def build_cases(args) -> List[Tuple[str, np.ndarray]]:
144 cases: List[Tuple[str, np.ndarray]] = []
145 requested = [c.strip().lower() for c in args.cases.split(",")]
146
147 for c in requested:
148 if c == "identity":
149 cases.append(("identity", np.eye(args.n_dim, dtype=float)))
150 elif c.startswith("equicorr"):
151 rho = args.rho
152 cases.append((f"equicorr_rho_{rho}".replace(".", "_"), equicorr(args.n_dim, rho)))
153 elif c == "block":
154 R = np.array([[1.0, args.block_rho, 0.0],
155 [args.block_rho, 1.0, 0.0],
156 [0.0, 0.0, 1.0]], dtype=float)
157 cases.append((f"block_rho12_{args.block_rho}".replace(".", "_"), R))
158 elif c == "near":
159 rho = args.near_rho
160 cases.append((f"near_singular_rho_{rho}".replace(".", "_"), equicorr(args.n_dim, rho)))
161 elif c == "custom":
162 if not args.custom_matrix:
163 raise ValueError("--cases inclut 'custom' mais --custom-matrix est vide.")
164 R = load_matrix_txt(Path(args.custom_matrix))
165 cases.append(("custom", R))
166 else:
167 raise ValueError(f"Cas inconnu: {c}")
168 return cases
169
170
171def main():
172 p = argparse.ArgumentParser(description="Testeur Python pour le générateur C++ 'correlated_rng'.")
173 p.add_argument("--bin", type=str, default="./correlated_rng",
174 help="Chemin du binaire C++ (défaut: ./correlated_rng).")
175 p.add_argument("--cpp", type=str, default="",
176 help="Chemin du fichier source C++ (optionnel). S'il est fourni et que --bin n'existe pas, on compile.")
177 p.add_argument("--dist", type=str, default="gaussian", help="Nom de distribution à passer au binaire.")
178 p.add_argument("--samples", type=int, default=15000, help="Nombre d'échantillons par cas.")
179 p.add_argument("--seed-base", type=int, default=12345, help="Seed de base (on utilise seed_base+i).")
180 p.add_argument("--cases", type=str, default="identity,equicorr,block,near",
181 help="Liste de cas séparés par des virgules parmi: identity,equicorr,block,near,custom")
182 p.add_argument("--n-dim", type=int, default=3, help="Dimension pour identity/equicorr/near.")
183 p.add_argument("--rho", type=float, default=0.5, help="Rho pour equicorr.")
184 p.add_argument("--near-rho", type=float, default=0.999, help="Rho pour near (quasi-singulier).")
185 p.add_argument("--block-rho", type=float, default=0.8, help="Corrélation entre y1 et y2 dans le cas 'block'.")
186 p.add_argument("--custom-matrix", type=str, default="", help="Chemin vers une matrice custom (format C++).")
187 p.add_argument("--out-dir", type=str, default="./cpp_test_out", help="Dossier de sortie (CSV + PNG).")
188 p.add_argument("--no-plots", action="store_true", help="Ne pas générer de figures.")
189 args = p.parse_args()
190
191 bin_path = Path(args.bin)
192 cpp_path = Path(args.cpp) if args.cpp else None
193 out_dir = Path(args.out_dir)
194 out_dir.mkdir(parents=True, exist_ok=True)
195
196 if not bin_path.exists() and cpp_path is not None:
197 compile_cpp_if_needed(bin_path, cpp_path)
198
199 if not bin_path.exists():
200 raise FileNotFoundError(
201 f"Executable not found: {bin_path}\n"
202 f"→ Compile-le (g++ -std=c++17 -O2 correlated_rng.cpp -o correlated_rng)\n"
203 f"ou passe --cpp pour compiler automatiquement."
204 )
205
206 cases = build_cases(args)
207
208 rows = []
209 for idx, (name, R) in enumerate(cases, 1):
210 print(f"[{idx}/{len(cases)}] Cas '{name}' : génération de {args.samples} échantillons …")
211 Y = sample_many(bin_path, R, n_samples=args.samples, seed_base=args.seed_base, dist=args.dist)
212 stats = compute_stats(Y, R)
213 row = {"case": name, **stats}
214 rows.append(row)
215
216 np.save(out_dir / f"{name}_samples.npy", Y)
217 np.savetxt(out_dir / f"{name}_target_R.txt", R, fmt="%.6f")
218 np.savetxt(out_dir / f"{name}_empirical_corr.txt", np.corrcoef(Y, rowvar=False), fmt="%.6f")
219
220 if not args.no_plots:
221 plot_case(out_dir, name, Y, R)
222
223 df = pd.DataFrame(rows).set_index("case")
224 csv_path = out_dir / "summary.csv"
225 df.to_csv(csv_path, float_format="%.6g")
226
227 print("\n=== Résumé ===")
228 print(df.to_string())
229 print(f"\nRésultats enregistrés dans: {out_dir.resolve()}")
230 print(f"Résumé CSV: {csv_path.resolve()}")
231
232
233if __name__ == "__main__":
234 main()
std::string join(const std::vector< std::string > &v)
Joins a list of strings with ", ".
Definition SourceView.cpp:3
std::vector< std::string > split(const std::string &s, char delimiter)
Splits a string into parts using a single-character delimiter.
Definition Utils.cpp:8
List[Tuple[str, np.ndarray]] build_cases(args)
np.ndarray run_cpp_once(Path bin_path, np.ndarray R, str dist="gaussian", int seed=None)
Dict[str, float] compute_stats(np.ndarray Y, np.ndarray R)
plot_case(Path save_dir, str name, np.ndarray Y, np.ndarray R)
None compile_cpp_if_needed(Path bin_path, Path cpp_path)
np.ndarray sample_many(Path bin_path, np.ndarray R, int n_samples, int seed_base, str dist="gaussian")
np.ndarray equicorr(int n, float rho)
str matrix_to_stdin_payload(np.ndarray R)
np.ndarray load_matrix_txt(Path path)