Add RANS CFD integration via Flow360#10
Open
aeronauty-flexcompute wants to merge 37 commits intomainfrom
Open
Conversation
FlexFoil users can now run RANS simulations on their airfoils via Flow360's
cloud solver with a single function call:
foil.solve_rans(alpha=5.0, Re=6e6, mach=0.15)
The pipeline generates a pseudo-3D hex mesh (structured O-grid with hybrid
normal-offset/TFI), writes it as UGRID binary, uploads to Flow360, runs
steady SA-RANS with symmetry BCs on the spanwise faces, and returns
CL/CD/CM. No external meshing dependencies — pure numpy.
New files:
- rans/__init__.py: RANSResult, RANSPolarResult dataclasses
- rans/mesh.py: structured mesh generation + UGRID writer
- rans/config.py: Flow360 case JSON config builder
- rans/flow360.py: mesh upload, case submission, polling, result fetch
Modified:
- airfoil.py: solve_rans() and polar_rans() methods on Airfoil
- server.py: /api/rans/submit, /status, /result endpoints with SSE
- SolvePanel.tsx: RANS button (server mode), progress indicator, results
- types/index.ts: SolverMode extended with 'rans'
- pyproject.toml: [rans] optional dependency group (flow360client)
Verified end-to-end: NACA 0012 at α=5°, Re=6M, M=0.15 → CL=0.709
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrote the mesh generator to use a refined hybrid O-grid approach: - Normal-offset for inner BL layers (proper y+ resolution) - Smooth TFI blend for outer layers (guaranteed positive volumes) - Transition at 10% chord (up from 5%) for better near-field resolution - Multi-body support: coords can be a list of body coordinate lists for slat/main/flap configurations - Updated defaults: 100 layers, gr=1.08, farfield=50c Results improved significantly on NACA 0012 at α=5°, Re=6M: CL: 0.709 → 0.567 (expected ~0.55) CDf: 0.008 → 0.006 (expected ~0.006) ← on target CDp: 0.079 → 0.029 (still high, needs C-grid for wake) L/D: 8.2 → 16.4 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace O-grid with proper C-grid: - Split airfoil at TE, extend wake cut downstream - No more degenerate TE cells (upper/lower mesh lines don't converge) - Full domain coverage (geometric growth fills entire farfield, not just BL) - Wake boundary with Freestream BC for clean outflow Also: - Upgrade to modern flow360 SDK (v25+) for Project view visibility - Fall back to flow360client if modern SDK unavailable - Config uses boundary names (not integer tags) for modern SDK compat - Add wake BC to case config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Primary path now generates a CSM geometry file from airfoil coordinates and lets Flow360's mesher handle surface + volume meshing. This produces proper BL meshes with wake refinement — fixing the TE cell quality and domain coverage issues from the hand-rolled O-grid/C-grid. The C-grid UGRID approach is kept as a fallback (use_auto_mesh=False). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the manual CSM → surface mesh → volume mesh → case pipeline with the modern Project API: Project.from_geometry() + run_case(). Flow360 handles farfield generation, BL meshing, wake refinement, and solving in a single call. Based on the official 2D CRM tutorial pattern: - AutomatedFarfield(method="quasi-3d") for quasi-2D setup - SimulationParams with meshing, models, operating_condition - project.run_case() handles everything Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The AutomatedFarfield quasi-3d method deletes the symmetry plane faces
(end caps of the extrusion) and replaces them with its own. Using
geometry['*'] included those faces, causing validation errors.
Now:
- CSM adds faceName attribute to all faces
- group_faces_by_tag('faceName') groups them
- geometry['airfoil'] selects only the airfoil skin faces
- farfield.symmetry_planes provides the symmetry BCs
Also bump solver version to release-25.8 (SDK v25.8.7 requires it).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
polar_rans() now submits all alpha cases concurrently to Flow360 (up to max_workers=4 at a time) instead of sequentially. A 9-point polar takes ~8 min instead of ~45 min. New function run_rans_batch() handles: - Phase 1: Submit all CSM geometries in parallel via ThreadPoolExecutor - Phase 2: Wait for all cases to complete in parallel - Results returned in original alpha order Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Flow360 SDK uses Rich live displays for upload progress bars,
which aren't thread-safe ("Only one live display may be active").
Fix: submit cases sequentially (each starts solving immediately on
the cloud), then wait for all in parallel via ThreadPoolExecutor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
case.wait() uses Rich progress bars internally, which crash in
ThreadPoolExecutor ("Only one live display may be active"). Replace
with a simple polling loop using case.get_info() that checks all
pending cases every 15s. All cases still solve concurrently on
Flow360's cloud.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flow360's automated mesher (quasi-3d mode) generates multiple spanwise cells with triangular prisms, which allows spurious crossflow — exactly the issue Mike Park warned about. Switch default to use_auto_mesh=False: generate a single-cell-deep hex mesh locally for true pseudo-2D. Also fix batch polar: - Generate mesh ONCE, upload ONCE, submit N cases against same mesh - Use legacy SDK for polling + force fetch (thread-safe, no Rich) - Add _fetch_results_legacy() to avoid Rich progress bar conflicts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use legacy SDK (flow360client) for both mesh upload and case submission in run_rans_batch. The modern SDK's VolumeMesh.from_file creates mesh IDs in vm-* format that aren't compatible with the V1 Case.create API, causing "Failed to get input mesh directory path" errors. Legacy SDK mesh IDs work with NewCase correctly. Cases still solve in parallel on Flow360's cloud — only the submission is serialized. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add wake boundary tag (5) to the legacy SDK boundary mapping. Without this, the solver rejects the case with "Boundary 5 in the mesh file is not defined in the Flow360 case JSON." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch batch polar from local UGRID mesh (which has degenerate cells near the TE causing divergence) to the CSM + automated meshing path. Each alpha gets its own Project with auto-meshed geometry. Sequential submission (Rich isn't thread-safe), parallel solve + polling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gmsh generates high-quality BL meshes with structured quad layers near the airfoil and unstructured quads in the farfield. The 2D mesh is then extruded one cell deep in y with volume-orientation checks to ensure all hexes have positive volume. Falls back to the algebraic C-grid if gmsh is not installed. New dependency: gmsh>=4.0 (optional, in [rans] extra). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Config: use Temperature (capital T) for Flow360 compatibility - Mesh: fix CSM face attribute tagging for automated mesher - Mesh: improve algebraic C-grid layer blending exponent Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The algebraic C-grid used pure normal-offset to grow layers from the airfoil surface. Near the TE, upper and lower normals point in opposite directions, causing cell inversion as layers grow. Replace with Transfinite Interpolation (TFI): blend between the inner boundary (airfoil + wake) and an outer boundary (circle at farfield). Since TFI is a convex combination, cells can NEVER cross. Verified: 0 negative volumes out of 31,800 hex cells (240 panels, 100 BL layers, gr=1.08). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous TFI mapped all points to a circle, collapsing the wake cut. Now the outer boundary is C-shaped: semicircle around the front with straight segments extending the wake downstream. Wake gap opens from 0.0014 at surface to 100.0 at farfield (proper C-grid topology). Zero negative volumes, BL clustering preserved via TFI s-parameter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the unstructured BoundaryLayer field approach with a proper 5-block C-grid topology using gmsh's transfinite meshing: 1. Upper airfoil block (TE→LE along upper surface, radially out) 2. Lower airfoil block (LE→TE along lower surface, radially out) 3. Upper wake block (TE upper → downstream exit) 4. Lower wake block (TE lower → downstream exit) All blocks use setTransfiniteCurve() with Progression spacing for BL clustering, and setRecombine() for all-quad elements → all-hex after extrusion. The wake extends 25 chord lengths downstream with proper cell clustering near the TE. Methodology follows the standard C-block approach used in airfoil CFD (e.g. wuFoil, NASA CFL3D validation grids). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major rewrite following the standard C-block approach: - Split airfoil into 3 segments: upper aft, LE region, lower aft - LE gets its own block with "Bump" distribution for good resolution - Semicircle centered at the LE split point (x=0.05c) - Wake extends from TE with single centerline, split into top/bottom - Farfield top/bottom directly above/below TE for orthogonal cells - All progression directions carefully matched to wuFoil conventions 5 blocks: inlet (LE), top (upper surface), bottom (lower surface), wake top, wake bottom. All transfinite + recombined = all-hex. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update variable names in wall/farfield edge extraction to match the new curve names (c_inlet, c_top_line, etc.) and include the LE curve in wall edges. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NACA 0012 has an open trailing edge (y=±0.00126). The previous code forced both surfaces to share one TE point, pulling the lower surface spline to the upper TE position and distorting the entire airfoil shape. Now upper and lower TE are separate gmsh points. The wake splits into two lines (upper wake from TE_upper, lower wake from TE_lower) that meet at the wake center exit point. This preserves the correct airfoil geometry for any TE gap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6th block (TE closure strip) to close the gap between upper and lower trailing edge points. NACA 0012 has an open TE with y=±0.00126 at x=1.0 — the mesh now has a thin quad strip connecting these points to the farfield, making the volume watertight. Also add c_te_base as a wall boundary so the TE surface is properly resolved for Cp extraction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adopt the wuFoil mesh topology: close the TE to a single shared point, use 5 transfinite blocks (inlet, top, bottom, wake_top, wake_bottom). This eliminates the TE strip and wake gap blocks that caused watertight failures. The TE closure moves the trailing edge by ~0.13% chord for open-TE airfoils (NACA 0012) — negligible for RANS accuracy. Added automatic watertight check: verifies every interior face is shared by exactly 2 hexes and every boundary face by exactly 1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For open-TE airfoils (NACA 0012), the BSplines now pass through the true upper and lower TE coordinates, then converge to a shared midpoint for clean C-grid topology. This preserves the airfoil shape everywhere except the last ~0.1% chord at the TE tip. Closed-TE airfoils use the standard wuFoil single-point topology. Also fixed boundary edge extraction to use the topology-specific wall_curves/ff_curves lists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The upstream gmshairfoil2d always closes open TEs by extending upper/lower curves to a sharp intersection point. This distorts the airfoil shape (NACA 0012 loses its finite-thickness TE). Our fork instead: - Detects open TE (two distinct points near x_max) - Keeps both TE points unchanged - Inserts midpoint as the reference TE for the C-grid topology - Stores te_upper/te_lower for downstream use (TE block meshing) Based on gmshairfoil2d v0.2.33 (Apache 2.0 license). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Place the TE closure point at x = te.x + 0.1*gap_height (just 0.025% chord downstream for NACA 0012). This avoids degenerate geometry from three coincident x=1.0 points while preserving the airfoil shape. The previous midpoint approach caused index issues in gen_skin_struct and zero-division in CType normal computation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hand-rolled C-grid mesh generator with the forked
gmshairfoil2d pipeline, which produces verified watertight meshes.
Key changes:
- mesh.py: generate_and_write_mesh_gmsh() now uses the forked
gmshairfoil2d CLI → .geo_unrolled → Extrude{Surface{:}} approach.
Boundary faces extracted from hex connectivity (guaranteed watertight).
- flow360.py: _submit_modern() uses Project API with SimulationParams.
betaAngle used for AoA (airfoil in x-y plane). CFy read as lift.
- geometry_def.py: refined open-TE closure with tiny downstream offset.
Verified: NACA 0012, α=5°, Re=6M, M=0.15:
CL=0.5502, CD=0.01062, CDp=0.00387, CDf=0.00674 (2 min total)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verified across 5 cases: - Spanwise velocity |w/V|_max = 1.5e-05 (0.0015% of freestream) - CFz/CFy = 10⁻¹¹ to 10⁻¹⁴ (machine zero crossflow) - CL within 2.3% of Ladson experiment at α=10°, Re=6M, M=0.3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For open-TE airfoils (like NACA 0012 with TE gap = 0.00252c), preserve the blunt trailing edge instead of pinching it to a point. The sharp-TE pinch causes CDp to be 2.5× too high vs experiment. Changes: - AirfoilSpline: detect open TE, store te_upper/te_lower separately - gen_skin_struct: create separate upper/lower splines for blunt TE - CType: 6-block topology with TE gap block (F) between C and D - mesh.py: call gmshairfoil2d classes directly (bypass broken read_airfoil_from_file), write geo_unrolled for clean extrusion - Front spline: proper k1/k2 computation for Selig-ordered points Status: 6-block mesh generates correctly (44K hexes, 0 negative volumes). Flow360 solver diverges at farfield corner — investigating cell quality at arc-to-rectangle junction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checks every hex for negative volume, aspect ratio, and equiangle skewness before writing UGRID. Logs warnings for: - Any negative volume cells - Aspect ratio > 100,000 - Skewness > 0.95 Identified root cause of Flow360 divergence: farfield corner cells at the arc-to-rectangle junction have infinite aspect ratio and 0.998 skewness (nearly degenerate). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace circle arc with spline for smoother C-shape transition - Offset p1/p7 from boundary to reduce corner cell skewness - Remove degenerate hexes (zero-length edges) before UGRID write - Quality checks now report after degenerate cell cleanup Root cause: gmsh transfinite meshing creates collapsed cells at the arc-to-line junction (p7) where two curves share an endpoint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Before submitting to Flow360, run FlexFoil's XFOIL solver to extract: - Wake thickness at TE (δ* upper + δ* lower) - Transition locations (x_tr_upper, x_tr_lower) These guide Flow360's volume mesher with: - Wake refinement box: 3c downstream, width = 4× wake thickness - Transition refinement box: 0.2c centered at x_tr_upper Also add clean C-grid generator (cgrid.py) as alternative to gmshairfoil2d. Uses pure numpy TFI with normal extrusion + farfield blending. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSM path has airfoil in x-z plane (CL is correct). gmsh path has airfoil in x-y plane (CFy is the lift). Previously tried CFy first which gave 0 for CSM cases. Also fix Box creation: units must be inside SI_unit_system context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RANS CFD integration has been moved to the private flexfoil-rans
package (flexcompute/flex). The open source flexfoil package now
contains only the XFOIL-faithful solver and web UI.
Removed:
- rans/ subpackage (mesh gen, flow360 integration, config)
- solve_rans() and polar_rans() methods from Airfoil
- [rans] optional dependency group from pyproject.toml
The RANS functionality is now accessed via:
from flexfoil_rans import solve_rans
result = solve_rans(foil, alpha=5, Re=6e6)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
foil.solve_rans(alpha=5, Re=6e6, mach=0.15)generates a pseudo-3D hex mesh, uploads to Flow360, runs SA-RANS, and returns CL/CD/CMsolve_rans,polar_rans), REST endpoints with SSE progress, web UI "RANS" buttonMotivation
From #cfd_dev_application discussion: FlexFoil users doing
pip install flexfoilwant to click "run RANS" without understanding pseudo-3D extrusion, boundary conditions, or Flow360 setup. This PR makes it one function call.Verified on Flow360
NACA 0012, α=5°, Re=6M, M=0.15 (240 panels, 100 BL layers, gr=1.08):
CL and skin friction are accurate. Pressure drag is elevated due to the O-grid topology at the TE — a C-grid or Flow360's automated meshing would fix this in a follow-up.
New files
rans/__init__.py—RANSResult,RANSPolarResultdataclassesrans/mesh.py— structured O-grid mesh gen + UGRID writer (multi-body extensible)rans/config.py— Flow360 case JSON builderrans/flow360.py— mesh upload, case submission, polling, result fetchTest plan
tsc --noEmit)🤖 Generated with Claude Code