Cluster 3 Potential Flow PINN

A Physics-Informed Neural Network predicting velocity potential (φ) and stream function (ψ) for 2D potential flow around a rotating cylinder — the pre-image, under the exact Joukowski conformal map, of a Joukowski airfoil — at any angle of attack. Trained as part of the 9-cluster Scientific AI Cluster Orchestration Framework, which pairs this network with an exact symbolic ("Symetria") Joukowski solver and two physics-grounded safety audits under LangGraph supervision.

Architecture

Input (x, y, angle_of_attack_deg) — 3 features
Output (φ, ψ) — velocity potential, stream function
Hidden layers 5 × 128 neurons, Tanh activation
Parameters ~66,000
Circulation term Computed analytically (exact, via atan2), not learned

The network predicts only the smooth, single-valued doublet+uniform-flow part of φ (plus ψ). The multi-valued vortex/circulation contribution to φ — (Γ(α)/2π)·θ, a genuine topological feature of point-vortex potentials that no smooth feedforward network can represent without corrupting its local gradient near the branch cut — is added analytically. This decomposition was a real architecture fix made during training (see Training History below), not a design chosen upfront.

Quickstart

import torch
from huggingface_hub import hf_hub_download
from modeling import PotentialFlowPINN

ckpt_path = hf_hub_download("dave1368/cluster-03-potential-flow-pinn", "potential_flow_pinn.pt")
# weights_only=False: the checkpoint is a dict with metadata (model_state_dict
# plus training info), not a bare tensor, so torch's default-safe loader can't
# be used as-is. Only do this for checkpoints you trust the source of.
checkpoint = torch.load(ckpt_path, map_location="cpu", weights_only=False)

model = PotentialFlowPINN()
model.load_state_dict(checkpoint["model_state_dict"])  # checkpoint also carries training-time loss history, see training_metrics.json
model.eval()

# coords: (x, y, angle_of_attack_degrees)
coords = torch.tensor([[1.5, 0.5, 5.0]])
phi_psi = model(coords)
print(phi_psi)  # tensor([[phi, psi]])

Training data

Exact closed-form labels — no synthetic correlation needed. For each sampled (r, θ, α), the exact potential-flow-around-a-rotating-cylinder solution (uniform flow + doublet + circulation, with circulation set by the Kutta condition at the trailing edge) provides the ground-truth (φ, ψ):

  • 60,000 training points, 10,000 validation points
  • Domain: r ∈ [1, 5·R], θ ∈ [-Ï€, Ï€], α ∈ [-10°, 25°]
  • Final train loss: 3.80e-05 · Final val loss: 3.76e-05 (MSE, 3000 epochs)

Training history — a real architecture bug, not just hyperparameter tuning

The network originally took only (x, y), so angle of attack had nowhere to go as an input — the AoA slider in the app changed nothing about the computed flow, a genuine dead-input bug. Fixed by extending the network to (x, y, α) → (φ, ψ).

That alone wasn't enough: training on the raw (φ, ψ) labels directly still produced large surface-velocity errors, traced to φ's multi-valued circulation term — moving its branch cut from θ=0 to θ=π just relocated a band of large error from one side of the cylinder to the other, rather than removing it, confirming the issue was structural (a smooth network cannot represent a jump discontinuity's local gradient) rather than a training artifact. Fixed via the exact-analytic + learned-smooth-residual decomposition described above.

Validated against classical sources (post-deployment finding)

Cross-checked against Euler (1757), d'Alembert (1752), and Joukowski (1910) — the papers cited in this cluster's Master Specification. Full data tables in the Space README. Headline finding: independently re-deriving d'Alembert's zero-drag theorem exposed a genuine bug in how drag itself was computed downstream of this model (a force-rotation-into-flow-aligned-frame omission in the orchestrator, not a defect in this checkpoint) — this network's own true drag error, once correctly measured, is small and roughly constant across the full AoA range (0.13–0.35) rather than growing with angle as originally appeared.

Check Result
Joukowski (1910) exact mapping Exact solver matches independent recomputation to floating-point precision
Kutta condition self-consistency v_θ ≈ 0 (numerical precision) at trailing edge, α ∈ [−10°, 25°]
d'Alembert (1752) drag, exact solution (flow-aligned) ≈0 at every tested angle, as required
d'Alembert (1752) drag, this network (flow-aligned) 0.13–0.35 across α ∈ [−10°, 25°]
Pointwise φ, ψ vs. exact (32-pt grid, α=0°/15°) Mean |err|: φ=1.06, ψ=1.23 (field magnitudes ~20–115)
Euler/Laplace ∇²φ (r ≥ 1.5, away from body) Small relative to local velocity-gradient scale

Limitations

  • Pointwise accuracy degrades near the airfoil surface (r ≈ R), especially right at the stagnation point — the region of steepest field curvature.
  • Inviscid potential flow cannot represent boundary layers, separation, or stall — by construction (this is the content of d'Alembert's Paradox).
  • The d'Alembert audit doesn't really grade how well this model learned the physics — a barely-trained version of this network, whose output changes very little from point to point, would also pass, since it too produces near-zero drag. What the audit is actually good at is catching a badly broken model, not measuring how accurate a working one is.

Related

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support