-
Notifications
You must be signed in to change notification settings - Fork 0
fix(validation): implement RectBivariateSpline interpolation for cavity grid mismatch #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
838732d
793670a
b1c3b35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,3 +39,5 @@ validation/references/ | |
| # Example/optimiser output directories (all crates) | ||
| outputs/ | ||
| report/ | ||
| .venv/ | ||
| __pycache__/ | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,24 +48,24 @@ def run_cfd_python_cavity(Re: float = 100, nx: int = 65, ny: int = 65): | |||||||||||||||||||||||||||
| # Check if cfd_python has cavity solver | ||||||||||||||||||||||||||||
| if hasattr(cfd_python, 'CavitySolver2D'): | ||||||||||||||||||||||||||||
| solver = cfd_python.CavitySolver2D( | ||||||||||||||||||||||||||||
| reynolds=Re, | ||||||||||||||||||||||||||||
| nx=nx, ny=ny, | ||||||||||||||||||||||||||||
| Re=Re, | ||||||||||||||||||||||||||||
| tolerance=1e-6, | ||||||||||||||||||||||||||||
| max_iterations=20000 | ||||||||||||||||||||||||||||
| lid_velocity=1.0, | ||||||||||||||||||||||||||||
| cavity_size=1.0 | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| result = solver.solve() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| "u": np.array(result.u_field), | ||||||||||||||||||||||||||||
| "v": np.array(result.v_field), | ||||||||||||||||||||||||||||
| "p": np.array(result.p_field), | ||||||||||||||||||||||||||||
| "u": np.array(result.u_field) if hasattr(result, 'u_field') else np.zeros((ny, nx)), | ||||||||||||||||||||||||||||
| "v": np.array(result.v_field) if hasattr(result, 'v_field') else np.zeros((ny, nx)), | ||||||||||||||||||||||||||||
| "p": np.array(result.p_field) if hasattr(result, 'p_field') else np.zeros((ny, nx)), | ||||||||||||||||||||||||||||
| "u_centerline": np.array(result.u_centerline), | ||||||||||||||||||||||||||||
| "v_centerline": np.array(result.v_centerline), | ||||||||||||||||||||||||||||
| "x": np.array(result.x_coords), | ||||||||||||||||||||||||||||
| "y": np.array(result.y_coords), | ||||||||||||||||||||||||||||
| "converged": result.converged, | ||||||||||||||||||||||||||||
| "iterations": result.iterations, | ||||||||||||||||||||||||||||
| "residual": result.residual | ||||||||||||||||||||||||||||
| "iterations": getattr(result, 'iterations', 0), | ||||||||||||||||||||||||||||
| "residual": getattr(result, 'residual', 0.0) | ||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential shape mismatch when fields are missing. The default 🛡️ Proposed fix to derive shape from coordinates+ x_coords = np.array(result.x_coords)
+ y_coords = np.array(result.y_coords)
+ grid_shape = (len(y_coords), len(x_coords))
+
return {
- "u": np.array(result.u_field) if hasattr(result, 'u_field') else np.zeros((ny, nx)),
- "v": np.array(result.v_field) if hasattr(result, 'v_field') else np.zeros((ny, nx)),
- "p": np.array(result.p_field) if hasattr(result, 'p_field') else np.zeros((ny, nx)),
+ "u": np.array(result.u_field) if hasattr(result, 'u_field') else np.zeros(grid_shape),
+ "v": np.array(result.v_field) if hasattr(result, 'v_field') else np.zeros(grid_shape),
+ "p": np.array(result.p_field) if hasattr(result, 'p_field') else np.zeros(grid_shape),
"u_centerline": np.array(result.u_centerline),
"v_centerline": np.array(result.v_centerline),
- "x": np.array(result.x_coords),
- "y": np.array(result.y_coords),
+ "x": x_coords,
+ "y": y_coords,🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| print("WARN: CavitySolver2D not found in cfd_python - using placeholder") | ||||||||||||||||||||||||||||
|
|
@@ -103,9 +103,34 @@ def compare_solutions(cfd_python_result, external_result, Re: float): | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Ensure same grid size | ||||||||||||||||||||||||||||
| if cfd_python_result["u"].shape != ext_sol["u"].shape: | ||||||||||||||||||||||||||||
| print(f"WARN: Grid size mismatch: cfd_python {cfd_python_result['u'].shape} vs external {ext_sol['u'].shape}") | ||||||||||||||||||||||||||||
| # TODO: Interpolate if needed | ||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||
| print(f"WARN: Grid size mismatch: cfd_python {cfd_python_result['u'].shape} vs external {ext_sol['u'].shape}. Interpolating...") | ||||||||||||||||||||||||||||
| from scipy.interpolate import RectBivariateSpline | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # cfd_python_result original grid | ||||||||||||||||||||||||||||
| x_old = cfd_python_result["x"] | ||||||||||||||||||||||||||||
| y_old = cfd_python_result["y"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # external solver grid | ||||||||||||||||||||||||||||
| x_new = ext_sol["x"] | ||||||||||||||||||||||||||||
| y_new = ext_sol["y"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Interpolate fields | ||||||||||||||||||||||||||||
| spline_u = RectBivariateSpline(y_old, x_old, cfd_python_result["u"]) | ||||||||||||||||||||||||||||
| cfd_python_result["u"] = spline_u(y_new, x_new) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| spline_v = RectBivariateSpline(y_old, x_old, cfd_python_result["v"]) | ||||||||||||||||||||||||||||
| cfd_python_result["v"] = spline_v(y_new, x_new) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| spline_p = RectBivariateSpline(y_old, x_old, cfd_python_result["p"]) | ||||||||||||||||||||||||||||
| cfd_python_result["p"] = spline_p(y_new, x_new) | ||||||||||||||||||||||||||||
|
Comment on lines
+117
to
+125
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interpolation logic for the
Suggested change
Comment on lines
+106
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the solver returns grid coordinates in descending order or non-monotonic order, 🛡️ Proposed fix to validate coordinate ordering # cfd_python_result original grid
x_old = cfd_python_result["x"]
y_old = cfd_python_result["y"]
+
+ # RectBivariateSpline requires strictly ascending coordinates
+ if not (np.all(np.diff(x_old) > 0) and np.all(np.diff(y_old) > 0)):
+ print("ERROR: Grid coordinates must be strictly ascending for interpolation")
+ return None
# external solver grid
x_new = ext_sol["x"]
y_new = ext_sol["y"]🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Update centerlines | ||||||||||||||||||||||||||||
| cfd_python_result["u_centerline"] = cfd_python_result["u"][:, len(x_new)//2] | ||||||||||||||||||||||||||||
| cfd_python_result["v_centerline"] = cfd_python_result["v"][len(y_new)//2, :] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Update coordinates | ||||||||||||||||||||||||||||
| cfd_python_result["x"] = x_new | ||||||||||||||||||||||||||||
| cfd_python_result["y"] = y_new | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Compute L2 errors | ||||||||||||||||||||||||||||
| u_diff = cfd_python_result["u"] - ext_sol["u"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
hasattrandgetattrcalls are hiding a critical issue. TheCavityResult2Dobject returned bysolver.solve()does not expose theu_field,v_field,p_field,iterations, orresidualattributes. You can verify this by inspecting thePyCavityResult2Dstruct incrates/cfd-python/src/solver_2d/cavity.rs.As a result:
u,v, andpfields for thecfd_pythonsolution will always be initialized as arrays of zeros.iterationsandresidualwill always be their default values (0 and 0.0).This renders the subsequent validation comparison incorrect, as it will be comparing the external solution against zero fields.
To fix this, the
PyCavityResult2Dstruct in the Rust code needs to be updated to include and expose these fields from the underlying Rust solver result.