Source code for buffalo_panel.app.cli.panel2d_cli

"""Command-line interface for running and inspecting panel cases."""

from __future__ import annotations

import argparse
from pathlib import Path

import buffalo_wings.airfoil as bwa

from buffalo_panel.config import load_panel_case, solve_panel_case_artifact
from buffalo_panel.post import (
    export_surface_quantities_csv,
    load_solved_case_artifact,
    save_solved_case_artifact,
    solution_from_artifact,
)


[docs] def build_parser() -> argparse.ArgumentParser: """Build the command-line parser for the CLI workflows.""" parser = argparse.ArgumentParser( prog="panel2d_cli", description=( "Run one Buffalo Panel case or inspect one solved artifact." ), ) subparsers = parser.add_subparsers(dest="command") run_parser = subparsers.add_parser( "run", help="Solve one structured case file and save a solved artifact.", ) run_parser.add_argument( "case_file", help="Path to the input case YAML or JSON file.", ) run_parser.add_argument( "-o", "--output", help=( "Output YAML path for the solved artifact. Defaults to a file " "next to the input case." ), ) inspect_parser = subparsers.add_parser( "inspect", help="Inspect metadata and key outputs from one solved artifact.", ) inspect_parser.add_argument( "artifact_file", help="Path to the solved artifact YAML file.", ) inspect_parser.add_argument( "--include-results", action="store_true", help=( "Reconstruct the runtime post-processing solution and print " "integrated coefficients." ), ) return parser
def _default_output_path(case_path: Path) -> Path: """Return the default solved-artifact path for one case file.""" return case_path.with_name(f"{case_path.stem}.solution.yaml") def _default_surface_output_path(artifact_path: Path) -> Path: """Return the default surface-export CSV path for one artifact.""" return artifact_path.with_name(f"{artifact_path.stem}.surface.csv")
[docs] def run_case(args: argparse.Namespace) -> None: """Solve one structured case file and persist the solved artifact.""" case_path = Path(args.case_file) output_path = ( Path(args.output) if args.output is not None else _default_output_path(case_path) ) spec = load_panel_case(case_path) artifact = solve_panel_case_artifact(spec) save_solved_case_artifact(artifact, output_path) loaded_artifact = load_solved_case_artifact(output_path) results = solution_from_artifact(loaded_artifact) surface_output_path: Path | None = None if ( spec.post is not None and spec.post.surface is not None and spec.post.surface.quantities ): surface_output_path = _default_surface_output_path(output_path) export_surface_quantities_csv( results, surface_output_path, case_name=loaded_artifact.case.case_name, quantities=spec.post.surface.quantities, ) print(f"Saved solved artifact: {output_path}") if surface_output_path is not None: print(f"Saved surface CSV: {surface_output_path}") print(f"Case: {loaded_artifact.case.case_name}") print( "Geometry: " f"{bwa.AirfoilFactory.describe_spec(spec.geometry.bodies[0].airfoil).long}" ) print(f"Panels: {results.geometry.n_panels}") print(f"C_l pressure = {results.integrated.cl_pressure:.6e}") print(f"C_l circulation = {results.integrated.cl_circulation:.6e}") print(f"C_d pressure = {results.integrated.cd_pressure:.6e}") print(f"C_m pressure = {results.integrated.cm_pressure:.6e}")
[docs] def inspect_artifact(args: argparse.Namespace) -> None: """Print one solved artifact's metadata and optional recovered outputs.""" artifact = load_solved_case_artifact(args.artifact_file) print(f"Artifact: {args.artifact_file}") print(f"Artifact version: {artifact.artifact_version}") print(f"Case: {artifact.case.case_name}") print(f"Formulation: {artifact.formulation}") print(f"Backend: {artifact.backend}") print( "Units: " f"{artifact.case.length_unit}, " f"{artifact.case.time_unit}, " f"{artifact.case.angle_unit}" ) print(f"Geometry: {artifact.geometry.geometry_name}") print(f"Nodes: {len(artifact.geometry.x)}") print(f"Families: {len(artifact.families)}") print(f"Unknowns: {len(artifact.solution_vector)}") if args.include_results: results = solution_from_artifact(artifact) print(f"Panels: {results.geometry.n_panels}") print(f"C_l pressure = {results.integrated.cl_pressure:.6e}") print(f"C_l circulation = {results.integrated.cl_circulation:.6e}") print(f"C_d pressure = {results.integrated.cd_pressure:.6e}") print(f"C_m pressure = {results.integrated.cm_pressure:.6e}")
def main() -> None: """Dispatch the CLI command for running or inspecting artifacts.""" parser = build_parser() args = parser.parse_args() if args.command == "run": run_case(args) return if args.command == "inspect": inspect_artifact(args) return parser.print_help() if __name__ == "__main__": main()