Skip to content

Commit f3e561b

Browse files
Gromwudclaude
andcommitted
thesis: wave truth-alternative; metric/aggregator + analysis scripts; pic data-prep
- configs/wave.yaml: degenerate-quadratic truth_alternative (u_xx^2 = 48.68 u_xx u_tt - 591.98 u_tt^2) -- an exact algebraic consequence of the wave equation EPDE discovers; credited like the burgers_inviscid similarity form. - thesis_metrics/aggregate/ablation_aggregate/runner/profile_loop_stats/run: metric, aggregation, profiling updates. - New analysis scripts (vcoef stability/regularizer diagnostics + AC instability figs). - projects/pic/data/*: per-system data-prep tweaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6ff9565 commit f3e561b

25 files changed

Lines changed: 2741 additions & 61 deletions

projects/pic/data/ac/ac.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def ac_discovery(foldername, noise_level):
136136

137137
dimensionality = data.ndim - 1
138138

139-
epde_search_obj = EpdeSearch(use_solver=True, multiobjective_mode=True,
139+
epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True,
140140
use_pic=True, boundary=(5, 10), verbose_params = {'show_iter_idx' : True, 'show_iter_fitness' : True},
141141
coordinate_tensors=grid, device='cuda')
142142

@@ -218,5 +218,5 @@ def ac_discovery(foldername, noise_level):
218218
directory = os.path.dirname(os.path.realpath(__file__))
219219
ac_folder_name = os.path.join(directory)
220220

221-
AC_test(fit_operator, ac_folder_name, 0)
222-
# ac_discovery(ac_folder_name, 0)
221+
# AC_test(fit_operator, ac_folder_name, 0)
222+
ac_discovery(ac_folder_name, 0)

projects/pic/data/heat_solar/heat_solar.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ def hs_3d_discovery(foldername, noise_level):
378378
ac_folder_name = os.path.join(directory)
379379

380380
# hs_test(fit_operator, ac_folder_name, 0)
381-
hs_discovery(ac_folder_name, 0)
382-
# hs_2d_discovery(ac_folder_name, 0)
381+
# hs_discovery(ac_folder_name, 0)
382+
hs_2d_discovery(ac_folder_name, 0)
383383
# hs_3d_discovery(ac_folder_name, 0)
384+

projects/pic/data/kdv/kdv.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ def kdv_sga_discovery(foldername, noise_level):
421421
def kdv_sindy_discovery(foldername, noise_level):
422422
grid, data = kdv_sindy_data(os.path.join(foldername, 'kdv_sindy.mat'))
423423
noised_data = noise_data(data, noise_level)
424-
data_nn = load_pretrained_PINN(os.path.join(foldername, f'kdv_{noise_level}_ann.pickle'))
424+
# data_nn = load_pretrained_PINN(os.path.join(foldername, f'kdv_{noise_level}_ann.pickle'))
425425

426426
dimensionality = data.ndim - 1
427427

@@ -436,7 +436,7 @@ def kdv_sindy_discovery(foldername, noise_level):
436436
popsize = 16
437437

438438
epde_search_obj.set_moeadd_params(population_size=popsize,
439-
training_epochs=1)
439+
training_epochs=3)
440440

441441
custom_trigonometric_eval_fun = {
442442
'cos(t)sin(x)': lambda *grids, **kwargs: (np.cos(grids[0]) * np.sin(grids[1])) ** kwargs['power']}
@@ -476,8 +476,8 @@ def kdv_sindy_discovery(foldername, noise_level):
476476
from epde.operators.utils.default_parameter_loader import EvolutionaryParams
477477
print("CUDA available:", torch.cuda.is_available())
478478
# Operator = fitness.SolverBasedFitness # Replace by the developed PIC-based operator.
479-
Operator = fitness.PIC
480-
# Operator = fitness.L2LRFitness
479+
# Operator = fitness.PIC
480+
Operator = fitness.L2LRFitness
481481
params = EvolutionaryParams()
482482
operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4}
483483
# operator_params = {"penalty_coeff": 0.2, "pinn_loss_mult": 1e4}
@@ -488,11 +488,11 @@ def kdv_sindy_discovery(foldername, noise_level):
488488
directory = os.path.dirname(os.path.realpath(__file__))
489489
kdv_folder_name = os.path.join(directory)
490490

491-
KdV_test(fit_operator, kdv_folder_name, 0)
491+
# KdV_test(fit_operator, kdv_folder_name, 0)
492492
# KdV_h_test(fit_operator, kdv_folder_name, 0)
493493
# KdV_sga_test(fit_operator, kdv_folder_name, 0)
494494

495495
# kdv_discovery(kdv_folder_name, 0)
496496
# kdv_h_discovery(kdv_folder_name, 0)
497497
# kdv_sga_discovery(kdv_folder_name, 5)
498-
# kdv_sindy_discovery(kdv_folder_name, 0)
498+
kdv_sindy_discovery(kdv_folder_name, 0)

projects/pic/data/lorenz/lorenz.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,8 @@ def lorenz_discovery(noise_level):
279279
# print('operator_params ', operator_params)
280280
fit_operator = prepare_suboperators(Operator(list(operator_params.keys())), operator_params)
281281

282-
#lorenz_discovery(0)
283-
lorenz_test(fit_operator, noise_level=0)
282+
lorenz_discovery(0)
283+
# lorenz_test(fit_operator, noise_level=0)
284284

285285

286286
def get_pic_network_summary(operator):

projects/pic/data/lv/lv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ def lv_discovery(noise_level):
107107
epde_search_obj.set_preprocessor(default_preprocessor_type='FD',
108108
preprocessor_kwargs={})
109109

110-
popsize = 16
111-
epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=1)
110+
popsize = 32
111+
epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=5)
112112

113113
factors_max_number = {'factors_num': [1, 2], 'probas' : [0.8, 0.2]}
114114

115-
epde_search_obj.fit(data=[x, y], variable_names=['u', 'v'], max_deriv_order=(1,),
115+
epde_search_obj.fit(data=[x, y], variable_names=['u', 'v'], max_deriv_order=(2,),
116116
equation_terms_max_number=7, data_fun_pow=3, additional_tokens=[trig_tokens, grid_tokens],
117117
equation_factors_max_number=factors_max_number,
118118
eq_sparsity_interval=(1e-8, 1e-0)) #
@@ -135,4 +135,4 @@ def lv_discovery(noise_level):
135135
print('operator_params ', operator_params)
136136
fit_operator = prepare_suboperators(Operator(list(operator_params.keys())), operator_params)
137137

138-
#lv_discovery(0)
138+
lv_discovery(0)

projects/pic/data/ns/ns.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,10 @@ def ns_discovery(foldername, noise_level):
242242
# preprocessor_kwargs={'epochs_max' : 1e3})
243243
epde_search_obj.set_preprocessor(default_preprocessor_type='FD',
244244
preprocessor_kwargs={})
245-
popsize = 32
245+
popsize = 64
246246

247247
epde_search_obj.set_moeadd_params(population_size=popsize,
248-
training_epochs=5)
248+
training_epochs=30)
249249

250250
custom_grid_tokens = CacheStoredTokens(token_type='grid',
251251
token_labels=['t', 'x'],
@@ -289,5 +289,5 @@ def ns_discovery(foldername, noise_level):
289289
directory = os.path.dirname(os.path.realpath(__file__))
290290
ns_folder_name = os.path.join(directory)
291291

292-
ns_test(fit_operator, ns_folder_name, 0)
293-
# ns_discovery(ns_folder_name, 0)
292+
# ns_test(fit_operator, ns_folder_name, 0)
293+
ns_discovery(ns_folder_name, 0)

projects/pic/data/ode/ode.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,6 @@ def ODE_simple_discovery(foldername, noise_level):
227227
ode_folder_name = os.path.join(directory)
228228

229229
# ODE_test(fit_operator, ode_folder_name, 0)
230-
# ODE_discovery(ode_folder_name, 0)
231-
ODE_simple_discovery(ode_folder_name, 0)
230+
ODE_discovery(ode_folder_name, 0)
231+
# ODE_simple_discovery(ode_folder_name, 0)
232232

projects/pic/data/vdp/vdp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def vdp_discovery(foldername, noise_level):
147147
preprocessor_kwargs={})
148148

149149
popsize = 16
150-
epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=1)
150+
epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=5)
151151

152152
factors_max_number = {'factors_num': [1, 2], 'probas': [0.65, 0.35]}
153153

projects/pic/data/wave/wave.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,5 +197,5 @@ def wave_discovery(foldername, noise_level):
197197
directory = os.path.dirname(os.path.realpath(__file__))
198198
wave_folder_name = os.path.join(directory)
199199

200-
wave_test(fit_operator, wave_folder_name, 0)
201-
# wave_discovery(wave_folder_name, 0)
200+
# wave_test(fit_operator, wave_folder_name, 0)
201+
wave_discovery(wave_folder_name, 0)

projects/thesis/_numcheck.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""Numerical-correctness check of VaryingCoefSetup on the REAL 14-dataset
2+
inputs (the seeded-truth features/grids/g_func weights each system feeds the
3+
estimator). Verifies, per (system, variable):
4+
5+
A. G symmetric & finite (direct + super-Gram).
6+
B. super/from_full == direct construction (G, Phiy, yWy, score) -- the
7+
EqRPS fast path used in the live search.
8+
C. gamma_0 solves the weighted normal equations (|X^T W (y - X g0)| ~ 0) --
9+
robust to collinearity, unlike comparing to a separate WLS solve.
10+
D. Var(gamma_0) matches sigma^2 * diag((X^T W X)^-1) (reported; the
11+
equilibration floor makes it approximate on near-singular blocks).
12+
E. score finite, >= 0, no NaN/Inf anywhere.
13+
F. Parseval (exact, basis property): var(beta(x)) == NC_raw per feature.
14+
15+
Usage: python _numcheck.py [system ...] (default: all 14)
16+
Run, then delete."""
17+
from __future__ import annotations
18+
import os, sys, traceback
19+
20+
_THIS = os.path.dirname(os.path.abspath(__file__))
21+
_ROOT = os.path.abspath(os.path.join(_THIS, '..', '..'))
22+
for _p in (_ROOT, _THIS):
23+
if _p not in sys.path:
24+
sys.path.insert(0, _p)
25+
26+
import numpy as np
27+
import yaml
28+
import epde.globals as gv
29+
from epde.operators.common.stability import VaryingCoefSetup as VC
30+
from kdv_sindy_test import build_pool_only, _normalize_grid_labels
31+
from thesis_runner import load_config, pipeline_settings, _set_seeds
32+
from vcoef_stat_compare import _ALL
33+
from epde.interface.equation_translator import translate_equation
34+
35+
36+
def _inputs(system):
37+
"""[(var, Z (N,n_terms), target_idx, w (N,), grid_shape), ...] for truth."""
38+
cfg = load_config(system)
39+
_set_seeds(0)
40+
gv.set_gram_config('vcoef')
41+
search = build_pool_only(cfg, pipeline_settings('new'))
42+
coords, data, variable_names, dim = cfg.load_data()
43+
all_vars = list(variable_names)
44+
truth = yaml.safe_load(open(os.path.join(_THIS, 'configs', f'{system}.yaml')))
45+
teqs = truth.get('truth_equations') or []
46+
seeded = (teqs[0] if len(all_vars) == 1
47+
else {v: teqs[i] for i, v in enumerate(all_vars)})
48+
seeded = _normalize_grid_labels(seeded)
49+
soeq = translate_equation(seeded, search.pool, all_vars=all_vars)
50+
out = []
51+
for v in all_vars:
52+
eq = soeq.vals[v]
53+
eq.main_var_to_explain = v
54+
eq.weights_internal = np.ones(len(eq.structure) - 1)
55+
eq.weights_internal_evald = True
56+
eq.weights_final_evald = True
57+
eq.evaluate(normalize=False, return_val=False) # populate grid cache
58+
Z = np.vstack([t.evaluate(False, grids=None)
59+
for t in eq.structure]).T.astype(float)
60+
w = np.asarray(gv.grid_cache.g_func[gv.grid_cache.g_func_mask], float).reshape(-1)
61+
gshape = tuple(int(n) for n in gv.grid_cache.inner_shape)
62+
out.append((v, Z, int(eq.target_idx), w, gshape))
63+
return out
64+
65+
66+
def _check(v, Z, tgt, w, gshape):
67+
N, n_terms = Z.shape
68+
feat_idx = [i for i in range(n_terms) if i != tgt]
69+
Xf = Z[:, feat_idx]
70+
yt = Z[:, tgt]
71+
direct = VC(Xf, yt, w, gshape, main_var=v, fit_intercept=True)
72+
sup = VC.precompute_super(Z, w, gshape, main_var=v)
73+
ff = VC.from_full(sup, tgt)
74+
75+
m = {}
76+
# A. symmetric & finite
77+
G = direct.G
78+
m['Gsym'] = float(np.abs(G - G.T).max() / (np.abs(G).max() + 1e-30))
79+
m['finite'] = bool(np.all(np.isfinite(G)) and np.all(np.isfinite(direct.Phiy))
80+
and np.all(np.isfinite(sup['G_super'])))
81+
# B. super/from_full == direct
82+
m['dG'] = float(np.abs(ff.G - direct.G).max() / (np.abs(direct.G).max() + 1e-30))
83+
m['dPhiy'] = float(np.abs(ff.Phiy - direct.Phiy).max() / (np.abs(direct.Phiy).max() + 1e-30))
84+
m['dyWy'] = float(abs(ff.yWy - direct.yWy) / (abs(direct.yWy) + 1e-30))
85+
sc_d = direct.score(None)
86+
sc_f = ff.score(None)
87+
m['dscore'] = float(np.abs(sc_f - sc_d).max())
88+
# C. gamma_0 weighted normal equations
89+
sol = direct._solve_gammas(None)
90+
B = sol['B']; nf = sol['nf']
91+
g0 = sol['gamma'][np.arange(nf) * B] # const per feature (incl intercept)
92+
Xa = np.column_stack([Xf, np.ones(N)])
93+
XtWy = Xa.T @ (w * yt)
94+
resid = Xa.T @ (w * (yt - Xa @ g0))
95+
m['normeq'] = float(np.abs(resid).max() / (np.abs(XtWy).max() + 1e-30))
96+
# D. Var(gamma_0) vs sigma^2 diag((X^T W X)^-1)
97+
A = Xa.T @ (w[:, None] * Xa)
98+
Neff = float(w.sum())
99+
rss = max(float(yt @ (w * yt) - g0 @ XtWy), 0.0)
100+
sigma2 = rss / max(Neff - nf, 1.0)
101+
try:
102+
var_ref = sigma2 * np.diag(np.linalg.inv(A))
103+
var_vc = sol['var'][np.arange(nf) * B]
104+
rel = np.abs(var_vc - var_ref) / (np.abs(var_ref) + 1e-30)
105+
m['dVar'] = float(np.nanmax(rel))
106+
except np.linalg.LinAlgError:
107+
m['dVar'] = float('nan')
108+
m['condA'] = float(np.linalg.cond(A))
109+
# E. score sane
110+
m['score_ok'] = bool(np.all(np.isfinite(sc_d)) and np.all(sc_d >= -1e-9))
111+
# F. Parseval exact
112+
st = direct.beta_field_stats(None)
113+
g = sol['gamma']
114+
par = 0.0
115+
for i in range(nf):
116+
nc_raw = float(np.sum(g[i * B + 1:(i + 1) * B] ** 2))
117+
par = max(par, abs(st['std'][i] ** 2 - nc_raw) / (nc_raw + 1e-12))
118+
m['parseval'] = float(par)
119+
return m
120+
121+
122+
# tolerances
123+
TOL = dict(Gsym=1e-10, dG=1e-7, dPhiy=1e-7, dyWy=1e-9, dscore=1e-6,
124+
normeq=1e-6, parseval=1e-6)
125+
126+
127+
def verdict(m):
128+
bad = []
129+
if not m['finite']:
130+
bad.append('NONFINITE')
131+
if not m['score_ok']:
132+
bad.append('score')
133+
for k, t in TOL.items():
134+
if not (m[k] <= t):
135+
bad.append(f'{k}={m[k]:.1e}')
136+
return bad
137+
138+
139+
def main():
140+
systems = sys.argv[1:] or list(_ALL)
141+
n_ok = n_tot = 0
142+
for s in systems:
143+
try:
144+
rows = _inputs(s)
145+
except Exception as e:
146+
print(f"{s:18s} INPUT-ERROR {type(e).__name__}: {str(e)[:60]}")
147+
continue
148+
for (v, Z, tgt, w, gshape) in rows:
149+
n_tot += 1
150+
try:
151+
m = _check(v, Z, tgt, w, gshape)
152+
except Exception as e:
153+
print(f"{s:14s}/{v:3s} CHECK-ERROR {type(e).__name__}: {str(e)[:50]}")
154+
traceback.print_exc()
155+
continue
156+
bad = verdict(m)
157+
tag = f"{s}/{v}" if len(rows) > 1 else s
158+
if not bad:
159+
n_ok += 1
160+
print(f"{tag:18s} OK superGmax={m['dG']:.0e} normeq={m['normeq']:.0e} "
161+
f"parseval={m['parseval']:.0e} dVar={m['dVar']:.0e} condA={m['condA']:.0e}")
162+
else:
163+
print(f"{tag:18s} FAIL {', '.join(bad)} (condA={m['condA']:.0e})")
164+
print(f"\n==== numerically correct: {n_ok}/{n_tot} (system,var) blocks ====")
165+
166+
167+
if __name__ == '__main__':
168+
sys.exit(main())

0 commit comments

Comments
 (0)