Skip to content

Commit b9d7859

Browse files
authored
Merge pull request #78 from Gromwud/main
New datasets and refactoring
2 parents c4521c4 + 83c1ea4 commit b9d7859

155 files changed

Lines changed: 54709 additions & 735 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

epde/eq_mo_objectives.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,13 @@ def _complexity_single_eq(system, equation_key):
6868
# ``weights_final`` whenever the sparsity step zeros more than one
6969
# weight.
7070
equation = system.vals[equation_key]
71+
tgt = equation.target_idx
7172
eq_compl = 0
7273
for idx, term in enumerate(equation.structure):
73-
if idx < equation.target_idx:
74+
if idx < tgt:
7475
if not equation.weights_internal[idx] == 0:
7576
eq_compl += complexity_deriv(term.structure)
76-
elif idx > equation.target_idx:
77+
elif idx > tgt:
7778
if not equation.weights_internal[idx-1] == 0:
7879
eq_compl += complexity_deriv(term.structure)
7980
else:

epde/globals.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
# suppresses noise leakage into the non-constant energy.
5353
vc_modes_cache: dict = {}
5454
vc_k_max: int = 6
55-
vc_freq_coef: float = 1.0
55+
vc_freq_coef: float = 0.0
5656

5757
# When True, ``VaryingCoefSetup._solve_gammas`` solves the mode block
5858
# PER-FEATURE (block-diagonal in feature index) instead of jointly: cross-
@@ -73,6 +73,36 @@
7373
# and which masks weak terms). No effect in 'tstat' mode (no max_corr there).
7474
anchor_on_residual: bool = False
7575

76+
# Which estimator the INSTABILITY OBJECTIVE (the ``Instability`` filler /
77+
# ``equation_terms_stability`` Pareto axis) uses. Decoupled from
78+
# ``gram_mode``, which keeps governing the sparsity keep-rule unchanged.
79+
# None -> resolve from gram_mode ('vcoef' -> 'vcoef', 'axis' -> 'cv')
80+
# -- exact backward compatibility (the default).
81+
# 'vcoef' -> varying-coefficient NC/gamma_0^2 (the Hadamard default).
82+
# 'cv' -> axis-aligned sliding-window CV (var/mu^2).
83+
# 'survival' -> block-resampled coefficient survival
84+
# (sign-flip rate + MAD/|median| across refits).
85+
# 'tile' -> per-tile refits, between-tile dispersion MAD/|median|
86+
# (basis-free spatial inhomogeneity).
87+
# Set via ``set_instability_metric`` before ``build_search``.
88+
instability_metric = None
89+
90+
# RPS amplified-identity guard: during the right-part term-sweep, a candidate
91+
# target whose winning fit has amplification ratio
92+
# A = sum_j |c_j| * ||col_j|| / ||target col|| (nonzero terms + intercept)
93+
# above this cap is DECLINED -- the parasitic ``Lambda * (near-null identity
94+
# combination) = target`` shape, where huge mutually-cancelling coefficients
95+
# stretch the residual of a VALID analytical identity (e.g. the LV sum
96+
# identity du+dv = alpha*u - gamma*v) to imitate an unrelated target out of
97+
# derivative noise. Evidence base (truth-anchor sweep, 14 equations incl.
98+
# real data): true forms sit at A in [1.0, 6.65]; observed parasites at
99+
# ~7e2..1.6e6 -- the cap of 100 leaves 15x headroom above the worst truth
100+
# (a genuine stiff balance still passes) and 6x below the mildest parasite.
101+
# Identity-form refits (du = -dv + alpha*u - gamma*v, A ~ 2) are UNAFFECTED:
102+
# only the amplified-cancellation shape is declined, so valid identities stay
103+
# credited. None disables the guard.
104+
rps_amplification_cap = 100.0
105+
76106

77107
def set_gram_config(mode: str = 'vcoef'):
78108
"""Override the global Gram-construction mode before ``build_search``.
@@ -114,6 +144,50 @@ def set_anchor_on_residual(flag: bool = False):
114144
anchor_on_residual = bool(flag)
115145

116146

147+
def set_instability_metric(metric=None):
148+
"""Override the instability-objective estimator before ``build_search``.
149+
150+
Mirrors ``set_gram_config``: a process-level global read by the
151+
``Instability`` filler in ``epde.operators.common.objectives``. ``None``
152+
(default) resolves from ``gram_mode`` for exact backward compatibility;
153+
see :data:`instability_metric` for the estimator menu. The sparsity
154+
keep-rule keeps following ``gram_mode`` regardless.
155+
"""
156+
global instability_metric
157+
valid = (None, 'vcoef', 'cv', 'survival', 'tile')
158+
if metric not in valid:
159+
raise ValueError(
160+
f'instability_metric must be one of {valid}; got {metric!r}')
161+
instability_metric = metric
162+
163+
164+
def set_rps_amplification_cap(cap=100.0):
165+
"""Override the RPS amplified-identity guard before ``build_search``.
166+
167+
``cap`` is the maximum admissible amplification ratio ``A`` of a
168+
candidate right-part fit (see :data:`rps_amplification_cap`); ``None``
169+
disables the guard. Mirrors ``set_gram_config``: a process-level global
170+
read by ``EqRightPartSelector`` during the term-sweep.
171+
"""
172+
global rps_amplification_cap
173+
if cap is not None:
174+
cap = float(cap)
175+
if not np.isfinite(cap) or cap <= 1.0:
176+
raise ValueError(
177+
'rps_amplification_cap must be a finite value > 1 (true '
178+
f'forms reach A ~ 6.7) or None to disable; got {cap!r}')
179+
rps_amplification_cap = cap
180+
181+
182+
def resolve_instability_metric() -> str:
183+
"""The effective instability-objective estimator: the explicit
184+
``instability_metric`` override if set, else the one ``gram_mode``
185+
implies ('vcoef' -> 'vcoef', 'axis' -> 'cv')."""
186+
if instability_metric is not None:
187+
return instability_metric
188+
return 'vcoef' if gram_mode == 'vcoef' else 'cv'
189+
190+
117191
def init_caches(set_grids: bool = False, device = 'cpu'):
118192
"""
119193
Initialization global variables for keeping input data, values of grid and useful tensors such as evaluated terms

epde/integrate/deepxde_integration.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,9 @@ def pde(x, y):
298298
use_weights = getattr(eq, "weights_final_evald", False) and hasattr(eq, "weights_final")
299299
residual = y[:, eq_idx:eq_idx + 1] * 0.0
300300
all_terms = eq.structure
301+
tgt = eq.target_idx
301302
for term_idx, term in enumerate(all_terms):
302-
if term_idx == eq.target_idx:
303+
if term_idx == tgt:
303304
continue
304305
coeff = float(eq.weights_final[term_idx]) if use_weights else 1.0
305306
term_val = 1.0
@@ -309,7 +310,7 @@ def pde(x, y):
309310
residual += coeff * term_val
310311
if use_weights and len(eq.weights_final) > len(all_terms):
311312
residual += float(eq.weights_final[-1]) * (y[:, 0:1] * 0.0 + 1.0)
312-
target = eq.structure[eq.target_idx]
313+
target = eq.target
313314
target_val = 1.0
314315
for factor in target.structure:
315316
fv = self._factor_value_with_map(dde, factor, x, y, self.coord_map, var_idx_map)

epde/integrate/interface.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,10 @@ def adjust_shape(tensor, mode = 'NN'):
119119
grids = [torch.from_numpy(subgrid).to(self._device) for subgrid in grids]
120120
default_domain = False
121121

122+
tgt = equation.target_idx
122123
for term_idx, term in enumerate(equation.structure):
123-
if term_idx != equation.target_idx:
124-
if term_idx < equation.target_idx:
124+
if term_idx != tgt:
125+
if term_idx < tgt:
125126
weight = equation.weights_final[term_idx]
126127
else:
127128
weight = equation.weights_final[term_idx-1]
@@ -142,12 +143,13 @@ def adjust_shape(tensor, mode = 'NN'):
142143

143144
target_weight = -1 # torch.full_like(input = grids[0], fill_value = -1.).to(self._device)
144145

145-
target_form = self._term_solver_form(equation.structure[equation.target_idx], grids, default_domain, variables)
146+
target_term = equation.target
147+
target_form = self._term_solver_form(target_term, grids, default_domain, variables)
146148
target_form['coeff'] = target_form['coeff'] * target_weight
147149
# target_form['coeff'] = adjust_shape(target_form['coeff'], mode = mode)
148150
# print(f'target_form shape is {target_form["coeff"].shape}')
149151

150-
_solver_form[equation.structure[equation.target_idx].name] = target_form
152+
_solver_form[target_term.name] = target_form
151153

152154
return _solver_form
153155

epde/interface/equation_translator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def _(text_form : str, pool, all_vars: List[str], use_pic: bool = False):
4747
max_factors = len(factors)
4848
term_list.append(Term(pool, passed_term=factors, collapse_powers=False))
4949

50-
metaparameters={'terms_number': {'optimizable': False, 'value': len(term_list)},
50+
metaparameters={'max_terms_number': {'optimizable': False, 'value': len(term_list)},
5151
'max_factors_in_term': {'optimizable': False, 'value': max_factors}}
5252
for var_key in all_vars:
5353
metaparameters[('sparsity', var_key)] = {'optimizable': True, 'value': 0.}
@@ -92,7 +92,7 @@ def _(text_form : dict, pool, all_vars: List[str], use_pic: bool = False):
9292
max_factors = len(factors)
9393
term_list.append(Term(pool, passed_term=factors, collapse_powers=False))
9494

95-
metaparameters={'terms_number': {'optimizable': False, 'value': len(term_list)},
95+
metaparameters={'max_terms_number': {'optimizable': False, 'value': len(term_list)},
9696
'max_factors_in_term': {'optimizable': False, 'value': max_factors}}
9797
for var_key in all_vars:
9898
metaparameters[('sparsity', var_key)] = {'optimizable': True, 'value': 0.}
@@ -182,7 +182,7 @@ def __init__(self, lp_terms : Union[list, tuple, dict], rp_term : Union[list, tu
182182
terms_aggregated = lp_terms_translated + [rp_translated,]
183183
max_factors = max([len(term.structure) for term in terms_aggregated])
184184

185-
metaparameters={'terms_number': {'optimizable': False, 'value': len(term_list)},
185+
metaparameters={'max_terms_number': {'optimizable': False, 'value': len(term_list)},
186186
'max_factors_in_term': {'optimizable': False, 'value': max_factors}}
187187
for var_key in all_vars:
188188
metaparameters[('sparsity', var_key)] = {'optimizable': True, 'value': 0.}
@@ -213,7 +213,7 @@ def __init__(self, lp_terms : Union[list, tuple, dict], rp_term : Union[list, tu
213213
terms_aggregated = self.lp_terms_translated + [self.rp_translated,]
214214
max_factors = max([len(term.structure) for term in terms_aggregated])
215215

216-
metaparameters={'terms_number': {'optimizable': False, 'value': len(term_list)},
216+
metaparameters={'max_terms_number': {'optimizable': False, 'value': len(term_list)},
217217
'max_factors_in_term': {'optimizable': False, 'value': max_factors}}
218218
for var_key in all_vars:
219219
metaparameters[('sparsity', var_key)] = {'optimizable': True, 'value': 0.}

epde/interface/logger.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
def equations_match(eq_checked: Equation, eq_ref: Equation):
2020
def parse_equation(equation: Equation, eps = 1e-9):
2121
term_weights = []
22+
tgt = equation.target_idx
2223
for idx, term in enumerate(equation.structure):
23-
if idx < equation.target_idx:
24+
if idx < tgt:
2425
term_weights.append(equation.weights_final[idx])
25-
elif idx == equation.target_idx:
26+
elif idx == tgt:
2627
term_weights.append(1)
2728
else:
2829
term_weights.append(equation.weights_final[idx-1])

epde/loader.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# while the second represents attributes for manual reconstruction.
3434
TYPESPEC_ATTRS = {'SoEq' : (['tokens_for_eq', 'tokens_supp', 'latex_form'], ['vals']),
3535
'Factor' : (['latex_form', '_ann_repr', '_latex_constructor', '_evaluator'], []),
36-
'Equation' : (['pool', 'latex_form', '_history'], ['structure']), # , '_features', '_target'
36+
'Equation' : (['pool', 'latex_form', '_history', '_target_term'], ['structure']), # _target_term re-linked by index, see attrs_from_dict
3737
'Term' : (['pool', 'latex_form'], ['structure']), 'TFPool' : ([], []), 'cache' : ([], []),
3838
'ParetoLevels' : (['levels'], ['population']), 'Population' : ([], [])}
3939

@@ -145,7 +145,16 @@ def obj_to_pickle(obj, not_to_pickle: list = [], manual_pickle: list = []):
145145
get_typespec_attrs(elem)[1])}
146146
else:
147147
dict_to_pickle[slot] = elem
148-
148+
149+
# Persist the identity-tracked right-part target as a plain integer
150+
# position. The ``_target_term`` Term itself is excluded from pickling
151+
# (TYPESPEC_ATTRS) to avoid an identity break with the freshly
152+
# reconstructed structure; ``attrs_from_dict`` re-links it by index once
153+
# ``structure`` is rebuilt. Stored under the legacy ``'target_idx'`` key so
154+
# pre-rename pickles (which saved the int slot) load unchanged.
155+
if parse_obj_type(obj) == 'Equation':
156+
dict_to_pickle['target_idx'] = obj.target_idx
157+
149158
return dict_to_pickle
150159

151160
def attrs_from_dict(obj, attributes, except_attrs: dict = {}):
@@ -174,6 +183,22 @@ def attrs_from_dict(obj, attributes, except_attrs: dict = {}):
174183

175184
obj.manual_reconst(man_attr, attributes[man_attr]['elements'], except_attrs)
176185

186+
# Re-link the identity-tracked right-part target from the saved integer
187+
# position, AFTER ``structure`` was rebuilt by manual_reconst above. The
188+
# ``'target_idx'`` key covers both post-rename pickles (saved by
189+
# obj_to_pickle) and legacy pre-rename pickles (which stored the old int
190+
# slot under the same key). The ``target_idx`` property setter resolves the
191+
# int against the reconstructed structure into ``_target_term``.
192+
if parse_obj_type(obj) == 'Equation':
193+
ti = attributes.get('target_idx', None)
194+
if ti is not None and getattr(obj, 'structure', None):
195+
try:
196+
obj.target_idx = int(ti)
197+
except (IndexError, ValueError, TypeError):
198+
obj._target_term = None
199+
else:
200+
obj._target_term = None
201+
177202

178203
def temp_pickle_save(obj : Union[SoEq, Cache, TFPool, ParetoLevels, Population],
179204
not_to_pickle = [], manual_pickle = []):
@@ -194,14 +219,15 @@ def system_preset(pool: TFPool): # Validate correctness of attribute definitions
194219
return {'SoEq' : {'tokens_for_eq' : TFPool(pool.families_demand_equation),
195220
'tokens_supp' : TFPool(pool.families_equationless),
196221
'latex_form' : None},
197-
'Equation' : {'pool' : pool,
222+
'Equation' : {'pool' : pool,
198223
'latex_form' : None,
199-
'_history' : None},
200-
'Term' : {'pool' : pool,
224+
'_history' : None,
225+
'_target_term' : None},
226+
'Term' : {'pool' : pool,
201227
'latex_form' : None},
202-
'Factor' : {'_latex_constructor' : None,
203-
'_evaluator' : None}}
204-
228+
'Factor' : {'_latex_constructor' : None,
229+
'_evaluator' : None}}
230+
205231
@staticmethod
206232
def pool_preset():
207233
return {'TFPool' : {}}
@@ -216,12 +242,13 @@ def population_preset(pool: TFPool):
216242
'SoEq' : {'tokens_for_eq' : TFPool(pool.families_demand_equation),
217243
'tokens_supp' : TFPool(pool.families_equationless),
218244
'latex_form' : None},
219-
'Equation' : {'pool' : pool,
245+
'Equation' : {'pool' : pool,
220246
'latex_form' : None,
221-
'_history' : None},
222-
'Term' : {'pool' : pool,
247+
'_history' : None,
248+
'_target_term' : None},
249+
'Term' : {'pool' : pool,
223250
'latex_form' : None},
224-
'Factor' : {'_latex_constructor' : None,
251+
'Factor' : {'_latex_constructor' : None,
225252
'_evaluator' : None}}
226253

227254
@staticmethod
@@ -230,12 +257,13 @@ def pareto_levels_preset(pool: TFPool):
230257
'SoEq' : {'tokens_for_eq' : TFPool(pool.families_demand_equation),
231258
'tokens_supp' : TFPool(pool.families_equationless),
232259
'latex_form' : None},
233-
'Equation' : {'pool' : pool,
260+
'Equation' : {'pool' : pool,
234261
'latex_form' : None,
235-
'_history' : None},
236-
'Term' : {'pool' : pool,
262+
'_history' : None,
263+
'_target_term' : None},
264+
'Term' : {'pool' : pool,
237265
'latex_form' : None},
238-
'Factor' : {'_latex_constructor' : None,
266+
'Factor' : {'_latex_constructor' : None,
239267
'_evaluator' : None}}
240268

241269

epde/operators/common/coeff_calculation.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ def _legacy_evaluate_nonzero(objective: Equation):
5959
``(target, features)`` with ``features=None`` when every
6060
non-target slot was filtered to zero.
6161
"""
62-
target = objective.structure[objective.target_idx].evaluate(False)
62+
tgt = objective.target_idx
63+
target = objective.target.evaluate(False)
6364
feats = []
6465
for term_idx, term in enumerate(objective.structure):
65-
if term_idx == objective.target_idx:
66+
if term_idx == tgt:
6667
continue
67-
wi_pos = (term_idx if term_idx < objective.target_idx
68+
wi_pos = (term_idx if term_idx < tgt
6869
else term_idx - 1)
6970
if objective.weights_internal[wi_pos] != 0:
7071
feats.append(term.evaluate(False))

0 commit comments

Comments
 (0)