Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8099885
tests
Gromwud May 7, 2026
8cbdf56
Merge pull request #3 from Yaroslav-Muravev/DeepXDEBasedFitness
Yaroslav-Muravev May 7, 2026
ef99211
Merge pull request #4 from Yaroslav-Muravev/DeepXDEBasedFitness
Yaroslav-Muravev May 7, 2026
01847d4
Merge pull request #5 from Yaroslav-Muravev/DeepXDEBasedFitness
Yaroslav-Muravev May 7, 2026
921b5a2
Update discovery.yml
Yaroslav-Muravev May 7, 2026
f8e5fc3
regularizer optimization
Gromwud May 12, 2026
5ef9718
Merge remote-tracking branch 'origin/main'
Gromwud May 12, 2026
2aeefac
Merge branch 'ITMO-NSS-team:main' into main
Gromwud May 12, 2026
014bb8d
Merge pull request #70 from Gromwud/main
Gromwud May 12, 2026
06a2511
Merge pull request #69 from Yaroslav-Muravev/DeepXDEBasedFitness
Gromwud May 12, 2026
758115b
Merge remote-tracking branch 'origin/main'
Gromwud May 12, 2026
21dbd34
moeadd: faster ndl_update + bounded TFPool + quieter prints
Gromwud May 20, 2026
fa3efe5
Equation/term mutation: bounded loops, terms_labels cache, stop-on-ex…
Gromwud May 20, 2026
20fbf66
RPS bidirectional uniqueness + fitness/sparsity/optimization refactor
Gromwud May 20, 2026
d753fa6
data loaders: ac / kdv / ns / vdp tweaks
Gromwud May 20, 2026
4cf50fe
migrate thesis runners to projects/thesis/ with YAML configs
Gromwud May 20, 2026
777d6db
trig tokens: quantized-freq structural identity
Gromwud May 20, 2026
b4e8766
deepxde: defer import-time backend banner
Gromwud May 20, 2026
8bfc879
Merge branch 'main' into main
Gromwud May 20, 2026
a3f8700
Merge pull request #71 from Gromwud/main
Gromwud May 20, 2026
88f6d6d
Merge branch 'ITMO-NSS-team:main' into main
Yaroslav-Muravev May 20, 2026
63d1037
trig tokens: fix Equation.terms_labels delegating to Term
Gromwud May 20, 2026
d4a5af4
thesis runner: extract defaults.yaml, add profiling entry points
Gromwud May 21, 2026
75a649b
perf: Phase 0 loop instrumentation gated by EPDE_LOOP_STATS
Gromwud May 21, 2026
6c1850c
perf: Tier 1 deepcopy aliasing for shared/immutable slots
Gromwud May 21, 2026
10719e9
perf: Equation.restore_property prefers ADD over REPLACE
Gromwud May 21, 2026
128c573
perf: EqRightPartSelector negative cache for known-bad structures
Gromwud May 21, 2026
53fd805
perf: L2LRFitness skips CV computation when force_out_of_place=True
Gromwud May 21, 2026
de55580
perf: Tier 3 per-equation super-Gram for EqRPS term-sweep
Gromwud May 21, 2026
0d2e888
perf: CustomEvaluator skips np.vectorize when funcs vectorize natively
Gromwud May 21, 2026
31b13f1
perf: key tensor cache on structural_label so bucketed trig shares en…
Gromwud May 21, 2026
bc9f5d9
fix: cap InitialParetoLevelSorting uniqueness retry loop
Gromwud May 21, 2026
2f1575e
fix: TermMutation reverts to pre-mutation term on cap-hit
Gromwud May 21, 2026
0b84631
fix: InitialParetoLevelSorting raises on cap-hit instead of accepting…
Gromwud May 21, 2026
905b69c
fix: EquationCrossover reverts to parents on duplicate-producing offs…
Gromwud May 21, 2026
00b668a
fix: restore_property raises on cap-hit instead of silently returning
Gromwud May 21, 2026
7f33edb
fix: restore term-replace mutation in multi-objective EquationMutation
Gromwud May 21, 2026
21589ff
fix: hybrid random-partition + TermParamCrossover EquationCrossover
Gromwud May 21, 2026
0b88ade
fix: D5 delete_point defensive assert + D6 shuffle MOEA/D sector order
Gromwud May 21, 2026
94ecba3
refactor: caching & structures consolidation (R1-R5 audit)
Gromwud May 21, 2026
7e5ac32
docs: MOEA/DD audit alignment (H1-H2 docstrings, M1-M2 defaults)
Gromwud May 21, 2026
e5979c3
Merge remote-tracking branch 'origin/main'
Gromwud May 21, 2026
b4e1d54
Merge pull request #72 from Gromwud/main
Gromwud May 21, 2026
0292757
Merge branch 'ITMO-NSS-team:main' into main
Yaroslav-Muravev May 22, 2026
8278a54
fix: EPDE pipeline correctness + ablation-cell wiring
Gromwud May 25, 2026
ac1c8cf
thesis: runner + per-system config refinements
Gromwud May 25, 2026
e22a15d
thesis: aggregators + plotting + pysindy comparison
Gromwud May 25, 2026
df3b6d5
docker: 8-cell parallel thesis sweep via docker-compose
Gromwud May 25, 2026
9d31d4b
test: regression for _complexity_single_eq weights_internal fix
Gromwud May 25, 2026
3750c36
thesis: per-system config refinements
Gromwud May 26, 2026
9817485
thesis: aggregators emit mean+-std for runtime, unique candidates, ep…
Gromwud May 26, 2026
854849a
thesis plots: joint history-match, common legends, per-equation stars
Gromwud May 26, 2026
40380a1
thesis: per-seed failure table
Gromwud May 26, 2026
0b2a9f5
Merge branch 'ITMO-NSS-team:main' into main
Gromwud May 26, 2026
2127824
Merge pull request #73 from Gromwud/main
Gromwud May 26, 2026
016d1f5
Merge branch 'ITMO-NSS-team:main' into main
Yaroslav-Muravev May 29, 2026
1ace42f
Merge branch 'DeepXDEBasedFitness' into main
Yaroslav-Muravev May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
.gitignore
.claude
.idea
projects/thesis/results
projects/thesis/_test_archive
__pycache__
**/__pycache__
*.pyc
*.pdf
*.rar
Master_Thesis__Bavshin_.pdf
moeadd.pdf
1 change: 1 addition & 0 deletions .github/workflows/discovery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install torch
pip install pytest-xdist pytest-split pytest-timeout
echo "PYTHONPATH=$PYTHONPATH:$(pwd)" >> $GITHUB_ENV

- name: Run discovery tests
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,8 @@ dmypy.json
.pyre/
#cache
/cache/*.tar

# Thesis run outputs (regenerated by projects/thesis/run.py + aggregators)
projects/thesis/results/
projects/thesis/thesis_summary.json
projects/thesis/thesis_ablation_summary.json
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# python:3.10-slim has glibc, just enough to install the wheels.
# numpy / scipy / sklearn / torch all ship manylinux wheels, so we
# don't need a build toolchain at runtime.
FROM python:3.10-slim

# libgomp1 is needed by numpy/sklearn/torch for OpenMP threading.
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /work

# Install Python deps first so the layer caches across source edits.
COPY requirements.txt /work/requirements.txt
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt

# Copy the repo last so source edits don't bust the dep cache.
COPY . /work

# Default to interactive shell; the actual sweep command is supplied
# by docker-compose via the per-service ``command:`` field.
ENTRYPOINT ["/bin/bash", "-l"]
58 changes: 58 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 8 containers, one per ablation cell. All share the same image
# and bind-mount projects/thesis/results/ so reps land on the host
# filesystem and the aggregator can read them without entering a
# container.
#
# Recommended invocation:
# docker compose build
# docker compose up -d # runs all 8 in background
# docker compose logs -f cell-new # tail one cell's output
# docker compose down # stops everything
#
# Resource notes:
# - 16 host cores -> 2 threads/container is safe.
# - 32 host cores -> bump OMP/MKL/OPENBLAS_NUM_THREADS to 4 for
# ~half the wall-clock.
# - The runner skips finished <cell>_rep<NN>.json files on
# resume, so containers can be killed and relaunched freely.

services:
cell-legacy: &cell-base
build: .
image: epde-thesis:latest
command: ["scripts/run_cell.sh", "legacy"]
volumes:
- ./projects/thesis/results:/work/projects/thesis/results
environment:
OMP_NUM_THREADS: "2"
MKL_NUM_THREADS: "2"
OPENBLAS_NUM_THREADS: "2"
restart: unless-stopped

cell-wape:
<<: *cell-base
command: ["scripts/run_cell.sh", "wape"]

cell-instab:
<<: *cell-base
command: ["scripts/run_cell.sh", "instab"]

cell-reg:
<<: *cell-base
command: ["scripts/run_cell.sh", "reg"]

cell-wape-instab:
<<: *cell-base
command: ["scripts/run_cell.sh", "wape_instab"]

cell-wape-reg:
<<: *cell-base
command: ["scripts/run_cell.sh", "wape_reg"]

cell-instab-reg:
<<: *cell-base
command: ["scripts/run_cell.sh", "instab_reg"]

cell-new:
<<: *cell-base
command: ["scripts/run_cell.sh", "new"]
106 changes: 106 additions & 0 deletions epde/_loop_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Lightweight retry/condition-loop instrumentation.

Off by default. Set ``EPDE_LOOP_STATS=1`` to enable; ~30 source-level
``record(...)`` call sites then accumulate per-loop stats that
``report()`` formats as a table.

Cost when disabled: a single global-var read per ``record`` call.
Cost when enabled: a dict lookup + list append per loop exit.
"""
from __future__ import annotations

import os
import sys
from collections import defaultdict
from typing import Optional

_ENABLED = os.environ.get('EPDE_LOOP_STATS', '0') == '1'


def _new_bucket():
return {'entries': 0, 'iters': [], 'hit_cap': 0, 'early_exit': 0, 'caps': set()}


_stats = defaultdict(_new_bucket)


def enabled() -> bool:
return _ENABLED


def record(site: str, iters: int, cap: int) -> None:
"""Record one loop exit.

``site`` is a human label like ``"EqRPS.outer"``. ``iters`` is the
number of iterations actually executed. ``cap`` is the loop's
maximum (use ``sys.maxsize`` for condition-driven loops with no
explicit cap).
"""
if not _ENABLED:
return
b = _stats[site]
b['entries'] += 1
b['iters'].append(iters)
b['caps'].add(cap)
if iters >= cap:
b['hit_cap'] += 1
elif iters <= 1:
b['early_exit'] += 1


def reset() -> None:
_stats.clear()


def _stats_for(name: str) -> dict:
b = _stats[name]
n = b['entries']
iters = b['iters']
if n == 0:
return {'entries': 0}
iters_sorted = sorted(iters)
median = iters_sorted[n // 2]
return {
'entries': n,
'mean': sum(iters) / n,
'median': median,
'max': max(iters),
'p95': iters_sorted[min(n - 1, int(n * 0.95))],
'total_iters': sum(iters),
'hit_cap_pct': 100.0 * b['hit_cap'] / n,
'early_exit_pct': 100.0 * b['early_exit'] / n,
'cap': max(b['caps']) if b['caps'] else 0,
}


def report(path: Optional[str] = None) -> str:
"""Format all recorded loops as a table, sorted by total iterations.

Writes to ``path`` if given AND also returns the string.
"""
sites = sorted(_stats.keys(),
key=lambda s: -sum(_stats[s]['iters']) if _stats[s]['iters'] else 0)
lines = []
header = (f"{'site':<45} {'entries':>8} {'mean':>7} {'med':>5} "
f"{'p95':>5} {'max':>5} {'cap':>6} {'%cap':>6} "
f"{'%early':>7} {'totIters':>10}")
lines.append(header)
lines.append('-' * len(header))
if not _ENABLED:
lines.append('(EPDE_LOOP_STATS disabled -- set EPDE_LOOP_STATS=1 to record)')
for site in sites:
s = _stats_for(site)
if s['entries'] == 0:
continue
cap_str = 'inf' if s['cap'] >= sys.maxsize else str(s['cap'])
lines.append(
f"{site:<45} {s['entries']:>8d} {s['mean']:>7.2f} "
f"{s['median']:>5d} {s['p95']:>5d} {s['max']:>5d} "
f"{cap_str:>6} {s['hit_cap_pct']:>5.1f}% "
f"{s['early_exit_pct']:>6.1f}% {s['total_iters']:>10d}"
)
text = '\n'.join(lines)
if path is not None:
with open(path, 'w') as f:
f.write(text + '\n')
return text
61 changes: 26 additions & 35 deletions epde/eq_mo_objectives.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,49 +60,40 @@ def equation_complexity_by_terms(system, equation_key):
return np.count_nonzero(system.vals[equation_key].weights_internal)


def equation_complexity_by_factors(system, equation_key):
'''
Evaluate the complexity of the system of PDEs, evaluating a number of factors in terms for each
equation. In the evaluation, we consider only terms with non-zero weights and target, while
the free coefficient is not included in the final metric. Also, the real-valued factors are
not considered in the result.

Parameters:
-----------
system - ``epde.structure.main_structures.SoEq`` object
The system, that is to be evaluated.

Returns:
----------
discrepancy : list of integers.
The values of the error metric: list entry for each of the equations.
'''
# eq_compl = 0

# for idx, term in enumerate(system.vals[equation_key].structure):
# if idx < system.vals[equation_key].target_idx:
# if not system.vals[equation_key].weights_final[idx] == 0:
# eq_compl += len(term.structure)
# elif idx > system.vals[equation_key].target_idx:
# if not system.vals[equation_key].weights_final[idx-1] == 0:
# eq_compl += len(term.structure)
# else:
# eq_compl += len(term.structure)
# return eq_compl
def _complexity_single_eq(system, equation_key):
# Index by ``weights_internal`` (always length ``len(structure)-1``,
# one entry per non-target term in structure order) rather than
# ``weights_final`` (zero-filtered to ``nnz+1`` by ``LASSOSparsity``
# and ``VWSRSparsity``): structure-position indexing breaks against
# ``weights_final`` whenever the sparsity step zeros more than one
# weight.
equation = system.vals[equation_key]
eq_compl = 0

for idx, term in enumerate(system.vals[equation_key].structure):
if idx < system.vals[equation_key].target_idx:
if not system.vals[equation_key].weights_final[idx] == 0:
for idx, term in enumerate(equation.structure):
if idx < equation.target_idx:
if not equation.weights_internal[idx] == 0:
eq_compl += complexity_deriv(term.structure)
elif idx > system.vals[equation_key].target_idx:
if not system.vals[equation_key].weights_final[idx-1] == 0:
elif idx > equation.target_idx:
if not equation.weights_internal[idx-1] == 0:
eq_compl += complexity_deriv(term.structure)
else:
eq_compl += complexity_deriv(term.structure)
return eq_compl


def equation_complexity_by_factors(system, equation_key=None):
'''
Evaluate the complexity of the system of PDEs as a number of factors in
non-zero terms for each equation, excluding the free coefficient and
real-valued factors. When ``equation_key`` is None, returns a per-equation
tuple matching the ``system.vars_to_describe`` order; otherwise the scalar
complexity for the named equation.
'''
if equation_key is None:
return tuple(_complexity_single_eq(system, k) for k in system.vars_to_describe)
return _complexity_single_eq(system, equation_key)


def equation_terms_stability(system, equation_key = None):
if equation_key:
assert system.vals[equation_key].stability_calculated
Expand Down
Loading
Loading