xfoil.compute_polar
Calculate lift and drag coefficients for airfoils by running XFOIL polar sweeps at specified Reynolds numbers and angles of attack.
Instructions
Run XFOIL for an airfoil at specified Reynolds angle of attack sweep. Input airfoil coordinates or NACA code plus sweep parameters. Returns lift/drag polar tables and solver metadata.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| request | Yes |
Implementation Reference
- src/xfoil_mcp/core.py:17-54 (handler)Core handler function that runs XFOIL subprocess, handles output, and returns formatted CSV polar data.def compute_polar(request: PolarRequest) -> PolarResponse: """Run XFOIL with the provided parameters and return a CSV payload.""" script, polar_path = _prepare_script(request) try: result = subprocess.run( [XFOIL_BIN], input=script.encode("utf-8"), cwd=polar_path.parent, check=False, capture_output=True, ) except FileNotFoundError as exc: # pragma: no cover raise RuntimeError("XFOIL binary not found") from exc if not polar_path.exists(): raise RuntimeError( "XFOIL did not create the polar output", ) if result.returncode != 0: # XFOIL often exits with code 2 even when the polar file is valid. We keep # the output but annotate the CSV with a comment so downstream users are # aware of the non-zero status. warning_comment = f"# xfoil exit code {result.returncode}" else: warning_comment = None lines = polar_path.read_text(encoding="utf-8").splitlines() if warning_comment: lines.insert(0, warning_comment) rows = list(csv.reader(lines)) if rows and rows[0] and "alpha" not in rows[0][0].lower(): rows.insert(0, ["alpha", "CL", "CD", "CM"]) csv_text = "\n".join(",".join(row) for row in rows) return PolarResponse(csv=csv_text)
- src/xfoil_mcp/tool.py:14-24 (registration)Tool registration using FastMCP @app.tool decorator, which delegates to compute_polar.@app.tool( name="xfoil.compute_polar", description=( "Run XFOIL for an airfoil at specified Reynolds angle of attack sweep. " "Input airfoil coordinates or NACA code plus sweep parameters. " "Returns lift/drag polar tables and solver metadata." ), meta={"version": "0.1.1", "categories": ["aero", "analysis"]}, ) def polar(request: PolarRequest) -> PolarResponse: return compute_polar(request)
- src/xfoil_mcp/models.py:8-17 (schema)Pydantic input schema defining parameters for the XFOIL polar computation.class PolarRequest(BaseModel): """Parameters required to run an XFOIL polar sweep.""" airfoil_name: str = Field(..., description="Identifier used when writing temporary files") airfoil_data: str = Field(..., description="Airfoil coordinate data in XFOIL DAT format") alphas: list[float] = Field(..., description="Angles of attack to analyse") reynolds: float = Field(..., gt=0.0, description="Reynolds number") mach: float = Field(0.0, ge=0.0, description="Mach number") iterations: int = Field(200, ge=10, le=10000, description="Maximum solver iterations")
- src/xfoil_mcp/models.py:19-23 (schema)Pydantic output schema for the CSV polar data response.class PolarResponse(BaseModel): """CSV payload returned by `compute_polar`.""" csv: str = Field(..., description="CSV text containing XFOIL polar data")
- src/xfoil_mcp/core.py:57-83 (helper)Helper function to prepare the XFOIL input script and temporary file paths.def _prepare_script(request: PolarRequest) -> tuple[str, Path]: tmpdir = Path(tempfile.mkdtemp(prefix="xfoil_mcp_")) airfoil_path = tmpdir / f"{request.airfoil_name}.dat" polar_path = tmpdir / "polar.txt" airfoil_path.write_text(request.airfoil_data, encoding="utf-8") airfoil_name = airfoil_path.name polar_name = polar_path.name commands: Iterable[str] = [ f"LOAD {airfoil_name}", "PANE", "OPER", f"VISC {request.reynolds}", f"MACH {request.mach}", f"ITER {request.iterations}", "PACC", polar_name, "", ] alpha_cmds = [f"ALFA {alpha}" for alpha in request.alphas] if not alpha_cmds: raise RuntimeError("At least one angle of attack must be supplied") footer = ["PACC", "", "QUIT"] script = "\n".join([*commands, *alpha_cmds, *footer]) + "\n" return script, polar_path