navier-cfd 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- navier_cfd/__init__.py +30 -0
- navier_cfd/__main__.py +3 -0
- navier_cfd/agents/__init__.py +3 -0
- navier_cfd/agents/orchestrator.py +114 -0
- navier_cfd/benchmarks/__init__.py +4 -0
- navier_cfd/benchmarks/metrics.py +69 -0
- navier_cfd/benchmarks/planning.py +76 -0
- navier_cfd/catalogs.py +208 -0
- navier_cfd/cli.py +229 -0
- navier_cfd/datasets/__init__.py +3 -0
- navier_cfd/datasets/huggingface.py +121 -0
- navier_cfd/evidence.py +404 -0
- navier_cfd/models/__init__.py +3 -0
- navier_cfd/models/base.py +56 -0
- navier_cfd/recommender.py +211 -0
- navier_cfd/specs.py +125 -0
- navier_cfd-0.2.0.dist-info/METADATA +253 -0
- navier_cfd-0.2.0.dist-info/RECORD +21 -0
- navier_cfd-0.2.0.dist-info/WHEEL +4 -0
- navier_cfd-0.2.0.dist-info/entry_points.txt +3 -0
- navier_cfd-0.2.0.dist-info/licenses/LICENSE +17 -0
navier_cfd/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""NAVIER-CFD public API: neural and agentic workflows for computational fluid dynamics."""
|
|
2
|
+
|
|
3
|
+
from .catalogs import Catalog
|
|
4
|
+
from .evidence import (
|
|
5
|
+
ALGORITHM_VERSION,
|
|
6
|
+
EvidenceMatch,
|
|
7
|
+
EvidenceRecord,
|
|
8
|
+
EvidenceSummary,
|
|
9
|
+
load_builtin_evidence,
|
|
10
|
+
score_model_evidence,
|
|
11
|
+
)
|
|
12
|
+
from .recommender import Recommendation, recommend_models
|
|
13
|
+
from .specs import DatasetSpec, ModelSpec, TaskSpec
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ALGORITHM_VERSION",
|
|
17
|
+
"Catalog",
|
|
18
|
+
"DatasetSpec",
|
|
19
|
+
"EvidenceMatch",
|
|
20
|
+
"EvidenceRecord",
|
|
21
|
+
"EvidenceSummary",
|
|
22
|
+
"ModelSpec",
|
|
23
|
+
"Recommendation",
|
|
24
|
+
"TaskSpec",
|
|
25
|
+
"load_builtin_evidence",
|
|
26
|
+
"recommend_models",
|
|
27
|
+
"score_model_evidence",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
__version__ = "0.2.0"
|
navier_cfd/__main__.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
from ..benchmarks.planning import build_benchmark_plan
|
|
9
|
+
from ..catalogs import Catalog
|
|
10
|
+
from ..recommender import recommend_models
|
|
11
|
+
from ..specs import TaskSpec
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LLMBackend(Protocol):
|
|
15
|
+
def __call__(self, *, system: str, user: str, tools: list[dict[str, Any]]) -> dict[str, Any]: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AgentPlan:
|
|
20
|
+
interpreted_task: TaskSpec
|
|
21
|
+
dataset_id: str
|
|
22
|
+
recommended_models: list[str]
|
|
23
|
+
benchmark_plan: dict[str, Any]
|
|
24
|
+
rationale: list[str]
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, Any]:
|
|
27
|
+
return asdict(self)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AgentOrchestrator:
|
|
31
|
+
"""Provider-neutral experiment planner.
|
|
32
|
+
|
|
33
|
+
The deterministic planner works offline. A caller may inject any LLM backend that accepts
|
|
34
|
+
the documented structured interface; no vendor SDK is required by the core package.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, catalog: Catalog | None = None, backend: LLMBackend | None = None) -> None:
|
|
38
|
+
self.catalog = catalog or Catalog.load_builtin()
|
|
39
|
+
self.backend = backend
|
|
40
|
+
|
|
41
|
+
def plan(self, prompt: str) -> AgentPlan:
|
|
42
|
+
task, dataset_id = self._interpret(prompt)
|
|
43
|
+
recommendations = recommend_models(task, self.catalog.models, top_k=8)
|
|
44
|
+
dataset = self.catalog.dataset(dataset_id)
|
|
45
|
+
benchmark = build_benchmark_plan(task, dataset, recommendations)
|
|
46
|
+
rationale = [
|
|
47
|
+
f"Dataset selected from explicit or inferred benchmark: {dataset.name}.",
|
|
48
|
+
"Models were hard-filtered by dimension and mesh compatibility.",
|
|
49
|
+
"Ranking rewards task role, physics overlap, geometry/temporal support, and safety needs.",
|
|
50
|
+
"The emitted plan separates interpolation, OOD, stability, physics, and cost metrics.",
|
|
51
|
+
]
|
|
52
|
+
return AgentPlan(
|
|
53
|
+
interpreted_task=task,
|
|
54
|
+
dataset_id=dataset_id,
|
|
55
|
+
recommended_models=[item.model.id for item in recommendations],
|
|
56
|
+
benchmark_plan=benchmark.to_dict(),
|
|
57
|
+
rationale=rationale,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _interpret(self, prompt: str) -> tuple[TaskSpec, str]:
|
|
61
|
+
text = prompt.lower()
|
|
62
|
+
dataset_id = "pdebench"
|
|
63
|
+
if "realpdebench" in text or "real pde" in text or "sim-to-real" in text:
|
|
64
|
+
dataset_id = "realpdebench"
|
|
65
|
+
elif "cfdbench" in text or "cavity" in text or "dam" in text:
|
|
66
|
+
dataset_id = "cfdbench"
|
|
67
|
+
|
|
68
|
+
dimension = 3 if re.search(r"\b3d\b|three-dimensional", text) else 2
|
|
69
|
+
mesh = "unstructured" if "unstructured" in text else "structured"
|
|
70
|
+
temporal = "autoregressive" if any(k in text for k in ["rollout", "forecast", "unsteady", "sequence"]) else "steady"
|
|
71
|
+
geometry = "varying" if any(k in text for k in ["geometry", "shape", "airfoil", "vehicle", "wing"]) else "fixed"
|
|
72
|
+
task_type = "acceleration" if any(k in text for k in ["accelerat", "precondition", "corrector", "solver-in-the-loop"]) else "surrogate"
|
|
73
|
+
problem = "cylinder_wake" if "cylinder" in text else "general_cfd"
|
|
74
|
+
memory = None
|
|
75
|
+
match = re.search(r"(\d+(?:\.\d+)?)\s*gb", text)
|
|
76
|
+
if match:
|
|
77
|
+
memory = float(match.group(1))
|
|
78
|
+
|
|
79
|
+
physics = ["incompressible_navier_stokes"]
|
|
80
|
+
if "compressible" in text or "mach" in text or "shock" in text:
|
|
81
|
+
physics = ["compressible_navier_stokes"]
|
|
82
|
+
if "multiphase" in text or "dam" in text:
|
|
83
|
+
physics.append("multiphase")
|
|
84
|
+
|
|
85
|
+
task = TaskSpec(
|
|
86
|
+
problem=problem,
|
|
87
|
+
task_type=task_type,
|
|
88
|
+
dimension=dimension,
|
|
89
|
+
mesh_type=mesh,
|
|
90
|
+
temporal_mode=temporal,
|
|
91
|
+
geometry_mode=geometry,
|
|
92
|
+
physics=tuple(physics),
|
|
93
|
+
requires_conservation="conserv" in text or task_type == "acceleration",
|
|
94
|
+
requires_uncertainty="uncertainty" in text or "safe" in text or "certif" in text,
|
|
95
|
+
requires_geometry_transfer="geometry" in text or "shape" in text,
|
|
96
|
+
requires_mesh_transfer="mesh transfer" in text or "cross-mesh" in text,
|
|
97
|
+
requires_long_rollout=temporal == "autoregressive",
|
|
98
|
+
hardware_memory_gb=memory,
|
|
99
|
+
notes=prompt,
|
|
100
|
+
)
|
|
101
|
+
return task, dataset_id
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def tool_schemas() -> list[dict[str, Any]]:
|
|
105
|
+
return [
|
|
106
|
+
{"name": "list_datasets", "description": "List registered CFD/PDE datasets."},
|
|
107
|
+
{"name": "inspect_dataset", "description": "Read one dataset card."},
|
|
108
|
+
{"name": "recommend_models", "description": "Rank compatible neural solvers."},
|
|
109
|
+
{"name": "build_benchmark_plan", "description": "Create splits, metrics and ablations."},
|
|
110
|
+
{"name": "emit_run_manifest", "description": "Serialize a reproducible experiment plan."},
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
def plan_json(self, prompt: str) -> str:
|
|
114
|
+
return json.dumps(self.plan(prompt).to_dict(), indent=2)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def rmse(pred: np.ndarray, target: np.ndarray) -> float:
|
|
9
|
+
return float(np.sqrt(np.mean((pred - target) ** 2)))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def mae(pred: np.ndarray, target: np.ndarray) -> float:
|
|
13
|
+
return float(np.mean(np.abs(pred - target)))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def relative_l2(pred: np.ndarray, target: np.ndarray, eps: float = 1e-12) -> float:
|
|
17
|
+
return float(np.linalg.norm(pred - target) / (np.linalg.norm(target) + eps))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def r2(pred: np.ndarray, target: np.ndarray, eps: float = 1e-12) -> float:
|
|
21
|
+
residual = np.sum((target - pred) ** 2)
|
|
22
|
+
total = np.sum((target - np.mean(target)) ** 2)
|
|
23
|
+
return float(1.0 - residual / (total + eps))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def spectral_relative_error(pred: np.ndarray, target: np.ndarray, eps: float = 1e-12) -> float:
|
|
27
|
+
axes = tuple(range(-min(2, pred.ndim), 0))
|
|
28
|
+
p = np.abs(np.fft.rfftn(pred, axes=axes))
|
|
29
|
+
t = np.abs(np.fft.rfftn(target, axes=axes))
|
|
30
|
+
return float(np.linalg.norm(p - t) / (np.linalg.norm(t) + eps))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def divergence_rms(velocity: np.ndarray, spacing: tuple[float, ...] | None = None) -> float:
|
|
34
|
+
"""Approximate RMS divergence for arrays shaped (..., spatial..., components)."""
|
|
35
|
+
if velocity.shape[-1] not in {2, 3}:
|
|
36
|
+
raise ValueError("velocity must store 2 or 3 components in the final axis")
|
|
37
|
+
ncomp = velocity.shape[-1]
|
|
38
|
+
spacing = spacing or tuple(1.0 for _ in range(ncomp))
|
|
39
|
+
if len(spacing) != ncomp:
|
|
40
|
+
raise ValueError("spacing length must equal the number of velocity components")
|
|
41
|
+
div = 0.0
|
|
42
|
+
for component in range(ncomp):
|
|
43
|
+
axis = velocity.ndim - ncomp - 1 + component
|
|
44
|
+
div = div + np.gradient(velocity[..., component], spacing[component], axis=axis)
|
|
45
|
+
return float(np.sqrt(np.mean(np.asarray(div) ** 2)))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def compute_metric_bundle(
|
|
49
|
+
pred: np.ndarray,
|
|
50
|
+
target: np.ndarray,
|
|
51
|
+
*,
|
|
52
|
+
velocity: bool = False,
|
|
53
|
+
spacing: tuple[float, ...] | None = None,
|
|
54
|
+
) -> dict[str, Any]:
|
|
55
|
+
pred = np.asarray(pred)
|
|
56
|
+
target = np.asarray(target)
|
|
57
|
+
if pred.shape != target.shape:
|
|
58
|
+
raise ValueError(f"shape mismatch: {pred.shape} != {target.shape}")
|
|
59
|
+
metrics: dict[str, Any] = {
|
|
60
|
+
"rmse": rmse(pred, target),
|
|
61
|
+
"mae": mae(pred, target),
|
|
62
|
+
"relative_l2": relative_l2(pred, target),
|
|
63
|
+
"r2": r2(pred, target),
|
|
64
|
+
"spectral_relative_error": spectral_relative_error(pred, target),
|
|
65
|
+
}
|
|
66
|
+
if velocity:
|
|
67
|
+
metrics["divergence_rms_pred"] = divergence_rms(pred, spacing)
|
|
68
|
+
metrics["divergence_rms_target"] = divergence_rms(target, spacing)
|
|
69
|
+
return metrics
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..recommender import Recommendation
|
|
8
|
+
from ..specs import DatasetSpec, TaskSpec
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class BenchmarkPlan:
|
|
13
|
+
task: TaskSpec
|
|
14
|
+
dataset: DatasetSpec
|
|
15
|
+
models: list[str]
|
|
16
|
+
splits: list[str]
|
|
17
|
+
metrics: list[str]
|
|
18
|
+
ablations: list[str]
|
|
19
|
+
reporting: list[str]
|
|
20
|
+
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> dict[str, Any]:
|
|
23
|
+
return asdict(self)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def build_benchmark_plan(
|
|
27
|
+
task: TaskSpec,
|
|
28
|
+
dataset: DatasetSpec,
|
|
29
|
+
recommendations: list[Recommendation],
|
|
30
|
+
max_models: int = 6,
|
|
31
|
+
) -> BenchmarkPlan:
|
|
32
|
+
metrics = [
|
|
33
|
+
"relative_l2",
|
|
34
|
+
"rmse",
|
|
35
|
+
"mae",
|
|
36
|
+
"r2",
|
|
37
|
+
"spectral_relative_error",
|
|
38
|
+
"wall_clock",
|
|
39
|
+
"peak_memory",
|
|
40
|
+
]
|
|
41
|
+
if task.temporal_mode in {"autoregressive", "unsteady", "sequence"}:
|
|
42
|
+
metrics += ["stable_horizon", "rollout_drift", "autocorrelation_error"]
|
|
43
|
+
if task.requires_conservation:
|
|
44
|
+
metrics += ["mass_balance_error", "momentum_balance_error", "divergence_rms"]
|
|
45
|
+
if task.requires_uncertainty:
|
|
46
|
+
metrics += ["coverage", "calibration_error", "risk_coverage"]
|
|
47
|
+
|
|
48
|
+
splits = ["interpolation", "parameter_ood"]
|
|
49
|
+
if task.requires_geometry_transfer or task.geometry_mode == "varying":
|
|
50
|
+
splits.append("geometry_ood")
|
|
51
|
+
if task.requires_mesh_transfer:
|
|
52
|
+
splits += ["mesh_resolution_ood", "mesh_generator_ood"]
|
|
53
|
+
if "real" in dataset.name.lower() or "real" in dataset.description.lower():
|
|
54
|
+
splits.append("sim_to_real")
|
|
55
|
+
|
|
56
|
+
return BenchmarkPlan(
|
|
57
|
+
task=task,
|
|
58
|
+
dataset=dataset,
|
|
59
|
+
models=[item.model.id for item in recommendations[:max_models]],
|
|
60
|
+
splits=splits,
|
|
61
|
+
metrics=list(dict.fromkeys(metrics)),
|
|
62
|
+
ablations=[
|
|
63
|
+
"remove_physics_features",
|
|
64
|
+
"remove_geometry_features",
|
|
65
|
+
"one_step_vs_unrolled_training",
|
|
66
|
+
"matched_parameter_and_wall_clock_budget",
|
|
67
|
+
],
|
|
68
|
+
reporting=[
|
|
69
|
+
"three_or_more_random_seeds",
|
|
70
|
+
"confidence_intervals_over_cases",
|
|
71
|
+
"data_generation_cost",
|
|
72
|
+
"training_cost",
|
|
73
|
+
"break_even_query_count",
|
|
74
|
+
"pinned_dataset_and_model_revisions",
|
|
75
|
+
],
|
|
76
|
+
)
|
navier_cfd/catalogs.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from .specs import DatasetSpec, ModelSpec
|
|
9
|
+
|
|
10
|
+
_MODEL_ROWS = [
|
|
11
|
+
("pinn", "Physics-Informed Neural Network", ("physics_informed", "general_pde_solver", "inverse")),
|
|
12
|
+
("nsfnets", "NSFnets", ("physics_informed", "specialized")),
|
|
13
|
+
("pinnsformer", "PINNsFormer", ("physics_informed", "general_pde_solver")),
|
|
14
|
+
("deeponet", "DeepONet", ("surrogate", "general_pde_solver", "physics_informed")),
|
|
15
|
+
("mionet", "MIONet", ("surrogate", "general_pde_solver")),
|
|
16
|
+
("fourier_deeponet", "Fourier-DeepONet", ("surrogate", "specialized")),
|
|
17
|
+
("nested_fourier_deeponet", "Nested Fourier-DeepONet", ("surrogate", "specialized", "particle_multiphase")),
|
|
18
|
+
("fourier_mionet", "Fourier-MIONet", ("surrogate", "specialized", "particle_multiphase")),
|
|
19
|
+
("fno", "Fourier Neural Operator", ("surrogate", "general_pde_solver")),
|
|
20
|
+
("pino", "Physics-Informed Neural Operator", ("physics_informed", "surrogate", "general_pde_solver")),
|
|
21
|
+
("geo_fno", "Geo-FNO", ("geometry", "surrogate", "general_pde_solver")),
|
|
22
|
+
("gino", "Geometry-Informed Neural Operator", ("geometry", "surrogate")),
|
|
23
|
+
("u_fno", "U-FNO", ("surrogate", "specialized", "particle_multiphase")),
|
|
24
|
+
("f_fno", "Factorized FNO", ("surrogate", "general_pde_solver")),
|
|
25
|
+
("u_no", "U-shaped Neural Operator", ("surrogate", "general_pde_solver")),
|
|
26
|
+
("lsm", "Latent Spectral Model", ("surrogate", "general_pde_solver")),
|
|
27
|
+
("gnot", "General Neural Operator Transformer", ("surrogate", "general_pde_solver", "geometry")),
|
|
28
|
+
("galerkin_transformer", "Galerkin Transformer", ("surrogate", "general_pde_solver")),
|
|
29
|
+
("mwt", "Multiwavelet Transformer", ("surrogate", "general_pde_solver")),
|
|
30
|
+
("factformer", "FactFormer", ("surrogate", "general_pde_solver")),
|
|
31
|
+
("ono", "Orthogonal Neural Operator", ("surrogate", "general_pde_solver")),
|
|
32
|
+
("transolver", "Transolver", ("geometry", "surrogate", "general_pde_solver")),
|
|
33
|
+
("upt", "Universal Physics Transformer", ("foundation", "geometry", "surrogate", "general_pde_solver")),
|
|
34
|
+
("meshgraphnets", "MeshGraphNets", ("geometry", "surrogate")),
|
|
35
|
+
("domino", "DoMINO", ("geometry", "surrogate", "specialized")),
|
|
36
|
+
("pibert", "PIBERT", ("surrogate", "physics_informed", "specialized")),
|
|
37
|
+
("fourierflow", "FourierFlow", ("surrogate", "generative", "specialized")),
|
|
38
|
+
("pde_refiner", "PDE-Refiner", ("surrogate", "generative", "general_pde_solver")),
|
|
39
|
+
("dpot", "DPOT", ("foundation", "surrogate", "general_pde_solver")),
|
|
40
|
+
("poseidon", "Poseidon", ("foundation", "surrogate", "general_pde_solver")),
|
|
41
|
+
("prose_fd", "PROSE-FD", ("foundation", "surrogate", "specialized")),
|
|
42
|
+
("bcat", "BCAT", ("foundation", "surrogate", "specialized")),
|
|
43
|
+
("pdeformer1", "PDEformer-1", ("foundation", "general_pde_solver")),
|
|
44
|
+
("pi_mfm", "PI-MFM", ("foundation", "physics_informed", "general_pde_solver")),
|
|
45
|
+
("laplace_no", "Laplace Neural Operator", ("surrogate", "general_pde_solver")),
|
|
46
|
+
("state_space_no", "State-Space Neural Operator", ("surrogate", "general_pde_solver")),
|
|
47
|
+
("p3d", "P3D", ("surrogate", "foundation", "specialized")),
|
|
48
|
+
("aerotransformer", "AeroTransformer", ("foundation", "geometry", "specialized")),
|
|
49
|
+
("tadpole", "Tadpole", ("foundation", "surrogate")),
|
|
50
|
+
("solver_in_loop", "Solver-in-the-Loop", ("acceleration", "physics_informed")),
|
|
51
|
+
("inc", "Indirect Neural Corrector", ("acceleration", "physics_informed")),
|
|
52
|
+
("pict", "PICT", ("acceleration", "specialized", "physics_informed")),
|
|
53
|
+
("diffsph", "diffSPH", ("acceleration", "particle_multiphase", "specialized")),
|
|
54
|
+
("neurosem", "NeuroSEM", ("acceleration", "physics_informed", "specialized")),
|
|
55
|
+
("np_newton", "Neural-Operator Preconditioned Newton", ("acceleration", "general_pde_solver")),
|
|
56
|
+
("geometry_preconditioner", "Geometry-Aware Neural Preconditioner", ("acceleration", "geometry")),
|
|
57
|
+
("neuraldem", "NeuralDEM", ("specialized", "particle_multiphase", "surrogate")),
|
|
58
|
+
("revit", "ReViT", ("geometry", "surrogate")),
|
|
59
|
+
("deepmmnet", "DeepM&Mnet", ("surrogate", "specialized", "physics_informed")),
|
|
60
|
+
("conformal_deeponet", "Conformalized-DeepONet", ("uncertainty", "surrogate")),
|
|
61
|
+
("tante", "TANTE", ("surrogate", "general_pde_solver")),
|
|
62
|
+
("riemannonet", "RiemannONet", ("physics_informed", "specialized", "surrogate")),
|
|
63
|
+
("energy_transformer", "Energy Transformer Flow Reconstruction", ("inverse", "surrogate", "specialized")),
|
|
64
|
+
("fun_diff", "FunDiff", ("generative", "physics_informed", "general_pde_solver")),
|
|
65
|
+
("flow_matching_pde", "Flow Matching for PDEs", ("generative", "physics_informed", "general_pde_solver")),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
_GEOMETRY = {"geo_fno", "gino", "gnot", "transolver", "upt", "meshgraphnets", "domino", "aerotransformer", "geometry_preconditioner", "revit"}
|
|
69
|
+
_THREED = {"gino", "upt", "domino", "p3d", "aerotransformer", "tadpole", "inc", "pict", "diffsph", "neurosem", "np_newton", "geometry_preconditioner", "neuraldem", "revit", "deepmmnet", "tante", "energy_transformer"}
|
|
70
|
+
_ACCEL = {"solver_in_loop", "inc", "pict", "diffsph", "neurosem", "np_newton", "geometry_preconditioner"}
|
|
71
|
+
_LONG = {"pinnsformer", "fno", "f_fno", "pibert", "fourierflow", "pde_refiner", "dpot", "poseidon", "prose_fd", "bcat", "p3d", "tadpole", "solver_in_loop", "inc", "neuraldem", "tante"}
|
|
72
|
+
_PARTICLE = {"diffsph", "neuraldem"}
|
|
73
|
+
_UQ = {"conformal_deeponet", "fun_diff", "fourierflow"}
|
|
74
|
+
|
|
75
|
+
_ARCH = {
|
|
76
|
+
"pinn": "coordinate network with PDE residuals",
|
|
77
|
+
"deeponet": "branch-trunk operator network",
|
|
78
|
+
"fno": "Fourier neural operator",
|
|
79
|
+
"pibert": "bidirectional transformer with Fourier-wavelet embeddings and physics-biased attention",
|
|
80
|
+
"fourierflow": "frequency-guided generative flow model",
|
|
81
|
+
"transolver": "physics-attention transformer on general geometries",
|
|
82
|
+
"upt": "latent universal physics transformer",
|
|
83
|
+
"gino": "graph lifting, latent FNO, and point projection",
|
|
84
|
+
"domino": "decomposable multiscale iterative neural operator",
|
|
85
|
+
"p3d": "scalable 3D global-context surrogate",
|
|
86
|
+
"aerotransformer": "pretrained transformer for 3D aerodynamics",
|
|
87
|
+
"inc": "equation-level indirect neural corrector",
|
|
88
|
+
"pict": "differentiable GPU multi-block PISO solver",
|
|
89
|
+
"diffsph": "differentiable smoothed-particle hydrodynamics",
|
|
90
|
+
"np_newton": "neural-operator preconditioned Newton solver",
|
|
91
|
+
"geometry_preconditioner": "geometry-aware neural iterative preconditioner",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_REF = {
|
|
95
|
+
"pibert": "Chakraborty, Pan & Chen, 2026",
|
|
96
|
+
"fno": "Li et al., ICLR 2021",
|
|
97
|
+
"deeponet": "Lu et al., Nature Machine Intelligence 2021",
|
|
98
|
+
"transolver": "Wu et al., ICML 2024",
|
|
99
|
+
"upt": "Alkin et al., NeurIPS 2024",
|
|
100
|
+
"gino": "Li et al., NeurIPS 2023",
|
|
101
|
+
"inc": "Wei et al., NeurIPS 2025",
|
|
102
|
+
"pict": "Franz et al., JCP 2025",
|
|
103
|
+
"diffsph": "Winchenbach & Thuerey, JCP 2026",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_REPO = {
|
|
107
|
+
"pibert": "https://github.com/Samsomyajit/pibert",
|
|
108
|
+
"fno": "https://github.com/neuraloperator/neuraloperator",
|
|
109
|
+
"geo_fno": "https://github.com/neuraloperator/Geo-FNO",
|
|
110
|
+
"transolver": "https://github.com/thuml/Neural-Solver-Library",
|
|
111
|
+
"inc": "https://github.com/tum-pbs/INC",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _models() -> list[ModelSpec]:
|
|
116
|
+
result: list[ModelSpec] = []
|
|
117
|
+
for model_id, name, categories in _MODEL_ROWS:
|
|
118
|
+
geometry = model_id in _GEOMETRY
|
|
119
|
+
three_d = model_id in _THREED or model_id not in {"pinnsformer", "riemannonet"}
|
|
120
|
+
accel = model_id in _ACCEL
|
|
121
|
+
particle = model_id in _PARTICLE
|
|
122
|
+
mesh_types = ("particle",) if particle else (("unstructured", "point_cloud", "structured") if geometry else ("structured", "meshfree"))
|
|
123
|
+
result.append(ModelSpec(
|
|
124
|
+
id=model_id,
|
|
125
|
+
name=name,
|
|
126
|
+
categories=categories,
|
|
127
|
+
architecture=_ARCH.get(model_id, name + " architecture"),
|
|
128
|
+
tasks=(("acceleration", "corrector", "preconditioner") if accel else ("surrogate", "forecasting")),
|
|
129
|
+
physics=(("particle", "granular") if particle else ("general_pde", "fluid_dynamics")),
|
|
130
|
+
mesh_types=mesh_types,
|
|
131
|
+
geometry_modes=(("varying", "parameterized") if geometry else ("fixed", "parameterized")),
|
|
132
|
+
temporal_modes=(("autoregressive", "unsteady", "steady") if model_id in _LONG or accel else ("steady", "unsteady")),
|
|
133
|
+
dimensions=((1, 2, 3) if three_d else (1, 2)),
|
|
134
|
+
strengths=("registered in the uniform NAVIER-CFD model taxonomy",),
|
|
135
|
+
limitations=("verify claims on the selected CFD benchmark and discretization",),
|
|
136
|
+
reference=_REF.get(model_id, name),
|
|
137
|
+
repository=_REPO.get(model_id),
|
|
138
|
+
integration="external" if model_id in _REPO else "metadata",
|
|
139
|
+
min_memory_gb=24 if model_id in {"gino", "domino", "p3d", "aerotransformer", "neuraldem"} else 8,
|
|
140
|
+
framework="pytorch",
|
|
141
|
+
tags=tuple(filter(None, ["mesh_transfer" if geometry else "", "long_rollout" if model_id in _LONG or accel else "", "uncertainty" if model_id in _UQ else "", "conservative" if accel else "", "3d" if three_d else ""])),
|
|
142
|
+
))
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _datasets() -> list[DatasetSpec]:
|
|
147
|
+
rows = [
|
|
148
|
+
("pdebench", "PDEBench", "Extensive time-dependent PDE benchmark", "AI4Science-WestlakeU/PDEBench", ("navier_stokes", "advection", "reaction_diffusion"), (1, 2, 3), ("structured",), ("fixed",), ("autoregressive",)),
|
|
149
|
+
("cfdbench", "CFDBench", "CFD benchmark with boundary, property, and geometry shifts", "chen-yingfa/CFDBench", ("cavity", "tube", "dam", "cylinder"), (2,), ("structured",), ("fixed", "varying"), ("steady", "autoregressive")),
|
|
150
|
+
("realpdebench", "RealPDEBench", "Paired real-world and simulated spatiotemporal systems", "AI4Science-WestlakeU/RealPDEBench", ("cylinder", "fsi", "controlled_cylinder", "foil", "combustion"), (2,), ("structured",), ("fixed", "varying"), ("autoregressive",)),
|
|
151
|
+
("airfrans", "AirfRANS", "RANS airfoil geometry and operating-condition benchmark", None, ("airfoil",), (2,), ("unstructured", "point_cloud"), ("varying",), ("steady",)),
|
|
152
|
+
("drivaernetpp", "DrivAerNet++", "Large multimodal vehicle CFD dataset", None, ("vehicle_aerodynamics",), (3,), ("point_cloud", "unstructured"), ("varying",), ("steady",)),
|
|
153
|
+
("drivaerml", "DrivAerML", "High-fidelity road-car aerodynamic CFD", None, ("vehicle_aerodynamics",), (3,), ("unstructured",), ("varying",), ("steady", "unsteady")),
|
|
154
|
+
("the_well", "The Well", "Diverse large-scale physics simulations", "polymathic-ai/the_well", ("fluid", "multiphysics"), (2, 3), ("structured",), ("fixed", "varying"), ("autoregressive",)),
|
|
155
|
+
("apebench", "APEBench", "Autoregressive PDE emulator benchmark", None, ("46_pde_configurations",), (1, 2, 3), ("structured",), ("fixed",), ("autoregressive",)),
|
|
156
|
+
("scalarflow", "ScalarFlow", "Real volumetric scalar transport flows", None, ("scalar_transport",), (3,), ("structured",), ("fixed",), ("autoregressive",)),
|
|
157
|
+
("shapenet_car", "ShapeNet-Car", "Vehicle-shape design benchmark", None, ("vehicle_design",), (3,), ("point_cloud",), ("varying",), ("steady",)),
|
|
158
|
+
("eagle", "EAGLE", "Fluid and geometry learning benchmark", None, ("fluid",), (2, 3), ("structured", "unstructured"), ("varying",), ("autoregressive",)),
|
|
159
|
+
]
|
|
160
|
+
return [DatasetSpec(
|
|
161
|
+
id=i, name=n, description=d, tasks=("surrogate", "benchmark"), physics=("fluid_dynamics", "general_pde"),
|
|
162
|
+
dimensions=dim, mesh_types=mesh, geometry_modes=geo, temporal_modes=temp,
|
|
163
|
+
hf_repo_id=hf, source_url=None, license=None, size=None, scenarios=scenarios,
|
|
164
|
+
notes=("Pin dataset revisions and preserve official splits.",)
|
|
165
|
+
) for i, n, d, hf, scenarios, dim, mesh, geo, temp in rows]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass
|
|
169
|
+
class Catalog:
|
|
170
|
+
models: list[ModelSpec]
|
|
171
|
+
datasets: list[DatasetSpec]
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def load_builtin(cls) -> "Catalog":
|
|
175
|
+
return cls(models=_models(), datasets=_datasets())
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def from_paths(cls, model_path: str | Path, dataset_path: str | Path) -> "Catalog":
|
|
179
|
+
model_data = json.loads(Path(model_path).read_text(encoding="utf-8"))
|
|
180
|
+
dataset_data = json.loads(Path(dataset_path).read_text(encoding="utf-8"))
|
|
181
|
+
return cls([ModelSpec.from_dict(x) for x in model_data], [DatasetSpec.from_dict(x) for x in dataset_data])
|
|
182
|
+
|
|
183
|
+
def model(self, model_id: str) -> ModelSpec:
|
|
184
|
+
for item in self.models:
|
|
185
|
+
if item.id == model_id:
|
|
186
|
+
return item
|
|
187
|
+
raise KeyError(f"Unknown model id: {model_id}")
|
|
188
|
+
|
|
189
|
+
def dataset(self, dataset_id: str) -> DatasetSpec:
|
|
190
|
+
for item in self.datasets:
|
|
191
|
+
if item.id == dataset_id:
|
|
192
|
+
return item
|
|
193
|
+
raise KeyError(f"Unknown dataset id: {dataset_id}")
|
|
194
|
+
|
|
195
|
+
def models_by_category(self, category: str) -> list[ModelSpec]:
|
|
196
|
+
return [x for x in self.models if category in x.categories]
|
|
197
|
+
|
|
198
|
+
def search_models(self, query: str) -> list[ModelSpec]:
|
|
199
|
+
q = query.lower()
|
|
200
|
+
return [x for x in self.models if q in x.name.lower() or q in x.architecture.lower() or any(q in t.lower() for t in x.tags)]
|
|
201
|
+
|
|
202
|
+
def extend_models(self, models: Iterable[ModelSpec]) -> None:
|
|
203
|
+
existing = {x.id for x in self.models}
|
|
204
|
+
for model in models:
|
|
205
|
+
if model.id in existing:
|
|
206
|
+
raise ValueError(f"Duplicate model id: {model.id}")
|
|
207
|
+
self.models.append(model)
|
|
208
|
+
existing.add(model.id)
|