Source code for buffalo_wings.airfoil.internal.sampling_export

"""Plain-data export helpers for sampled airfoil payloads."""

from __future__ import annotations

import json
from os import PathLike
from pathlib import Path
from typing import Literal, cast

import numpy as np
from buffalo_core.diagnostics import (
    Diagnostic,
    DiagnosticLocation,
    DiagnosticReport,
)
from buffalo_core.numeric import as_float_scalar
from buffalo_core.typing import FloatArray, FloatInput
from numpy.typing import NDArray

from .base import Airfoil
from .sampling import (
    AirfoilSurfaceSamples,
    SurfaceSamples,
    sample_airfoil,
    sample_airfoil_at_arc_length,
    sample_airfoil_at_xi,
)


[docs] def surface_samples_to_dict( samples: SurfaceSamples, *, include_parameters: bool = True, include_derivatives: bool = True, ) -> dict[str, object]: """ Convert one sampled airfoil surface to plain Python data. Parameters ---------- samples : SurfaceSamples Sampled one-surface payload produced by the public sampling API. include_parameters : bool, default=True Whether to include the sampled ``xi``, surface-local arc length, and native curve parameter vectors. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature. Returns ------- dict[str, object] Export-friendly mapping containing only plain Python scalars, booleans, lists, and nested lists. """ payload: dict[str, object] = { "coordinates": _float_matrix_to_list(samples.coordinates), "surface_length": float(as_float_scalar(samples.surface_length)), } if include_parameters: payload["xi"] = _float_vector_to_list(samples.xi) payload["arc_length"] = _float_vector_to_list(samples.arc_length) payload["curve_u"] = _float_vector_to_list(samples.curve_u) if include_derivatives: payload["slope"] = _float_vector_to_list(samples.slope) payload["slope_is_finite"] = _bool_vector_to_list( samples.slope_is_finite ) payload["curvature"] = _float_vector_to_list(samples.curvature) return payload
[docs] def airfoil_surface_samples_to_dict( samples: AirfoilSurfaceSamples, *, include_parameters: bool = True, include_derivatives: bool = True, include_diagnostics: bool = True, ) -> dict[str, object]: """ Convert a full two-surface sampling payload to plain Python data. Parameters ---------- samples : AirfoilSurfaceSamples Full downstream surface-sampling payload produced by the public sampling API. include_parameters : bool, default=True Whether to include sampled ``xi``, surface-local arc length, and native curve parameter vectors inside the lower and upper surface payloads. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature inside the lower and upper surface payloads. include_diagnostics : bool, default=True Whether to include serialized structured diagnostics. Returns ------- dict[str, object] Export-friendly mapping containing both sampled surfaces, point metadata, chord length, and optional serialized diagnostics. """ payload: dict[str, object] = { "lower": surface_samples_to_dict( samples.lower, include_parameters=include_parameters, include_derivatives=include_derivatives, ), "upper": surface_samples_to_dict( samples.upper, include_parameters=include_parameters, include_derivatives=include_derivatives, ), "leading_edge": _float_vector_to_list(samples.leading_edge), "trailing_edge_upper": _float_vector_to_list( samples.trailing_edge_upper ), "trailing_edge_lower": _float_vector_to_list( samples.trailing_edge_lower ), "trailing_edge_midpoint": _float_vector_to_list( samples.trailing_edge_midpoint ), "chord_length": float(as_float_scalar(samples.chord_length)), } if include_diagnostics: payload["diagnostics"] = _diagnostic_report_to_dict(samples.diagnostics) return payload
[docs] def airfoil_surface_samples_to_json( samples: AirfoilSurfaceSamples, *, include_parameters: bool = True, include_derivatives: bool = True, include_diagnostics: bool = True, indent: int = 2, sort_keys: bool = True, ) -> str: """ Convert a full sampled airfoil payload to a JSON string. Parameters ---------- samples : AirfoilSurfaceSamples Full downstream surface-sampling payload produced by the public sampling API. include_parameters : bool, default=True Whether to include sampled ``xi``, surface-local arc length, and native curve parameter vectors inside the lower and upper surface payloads. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature inside the lower and upper surface payloads. include_diagnostics : bool, default=True Whether to include serialized structured diagnostics. indent : int, default=2 Indentation level passed to ``json.dumps``. sort_keys : bool, default=True Whether to sort JSON mapping keys deterministically. Returns ------- str JSON serialization of the exported airfoil sampling payload. """ return json.dumps( airfoil_surface_samples_to_dict( samples, include_parameters=include_parameters, include_derivatives=include_derivatives, include_diagnostics=include_diagnostics, ), indent=indent, sort_keys=sort_keys, )
[docs] def write_airfoil_surface_samples( samples: AirfoilSurfaceSamples, *, path: str | PathLike[str], file_format: Literal["json", "selig", "lednicer"], title: str = "Buffalo Wings Airfoil", include_parameters: bool = True, include_derivatives: bool = True, include_diagnostics: bool = True, indent: int = 2, sort_keys: bool = True, ) -> Path: """ Write sampled airfoil surfaces to a file format. Parameters ---------- samples : AirfoilSurfaceSamples Full downstream surface-sampling payload produced by the public sampling API. path : str | os.PathLike[str] Destination file path for the exported output. file_format : {"json", "selig", "lednicer"} File format to emit. title : str, default="Buffalo Wings Airfoil" Title line written as the first non-ignored line of text-based coordinate files. include_parameters : bool, default=True Whether to include sampled ``xi``, surface-local arc length, and native curve parameter vectors when writing JSON. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature when writing JSON. include_diagnostics : bool, default=True Whether to include serialized structured diagnostics when writing JSON. indent : int, default=2 Indentation level passed to ``json.dumps`` when writing JSON. sort_keys : bool, default=True Whether to sort JSON mapping keys deterministically when writing JSON. Returns ------- pathlib.Path Resolved output path after writing the file. """ output_path = Path(path) if file_format == "json": output_path.write_text( airfoil_surface_samples_to_json( samples, include_parameters=include_parameters, include_derivatives=include_derivatives, include_diagnostics=include_diagnostics, indent=indent, sort_keys=sort_keys, ), encoding="utf-8", ) return output_path if file_format == "selig": contents = _airfoil_surface_samples_to_selig(samples, title=title) else: contents = _airfoil_surface_samples_to_lednicer(samples, title=title) output_path.write_text(contents, encoding="utf-8") return output_path
[docs] def write_sampled_airfoil( airfoil: Airfoil, *, path: str | PathLike[str], num_points_per_surface: int, spacing: Literal["uniform", "cosine"] = "cosine", file_format: Literal["json", "selig", "lednicer"], title: str = "Buffalo Wings Airfoil", include_parameters: bool = True, include_derivatives: bool = True, include_diagnostics: bool = True, indent: int = 2, sort_keys: bool = True, ) -> Path: """ Sample an airfoil and write the result to a file. Parameters ---------- airfoil : Airfoil Airfoil to sample. path : str | os.PathLike[str] Destination file path for the exported output. num_points_per_surface : int Number of sample points to generate on each surface. spacing : {"uniform", "cosine"}, default="cosine" Spacing rule used to distribute surface-local sample parameters. file_format : {"json", "selig", "lednicer"} File format to emit. title : str, default="Buffalo Wings Airfoil" Title line written as the first non-ignored line of text-based coordinate files. include_parameters : bool, default=True Whether to include sampled ``xi``, surface-local arc length, and native curve parameter vectors when writing JSON. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature when writing JSON. include_diagnostics : bool, default=True Whether to include serialized structured diagnostics when writing JSON. indent : int, default=2 Indentation level passed to ``json.dumps`` when writing JSON. sort_keys : bool, default=True Whether to sort JSON mapping keys deterministically when writing JSON. Returns ------- pathlib.Path Resolved output path after writing the file. """ return write_airfoil_surface_samples( sample_airfoil( airfoil, num_points_per_surface=num_points_per_surface, spacing=spacing, ), path=path, file_format=file_format, title=title, include_parameters=include_parameters, include_derivatives=include_derivatives, include_diagnostics=include_diagnostics, indent=indent, sort_keys=sort_keys, )
[docs] def write_sampled_airfoil_at_xi( airfoil: Airfoil, *, path: str | PathLike[str], lower: FloatInput, upper: FloatInput, file_format: Literal["json", "selig", "lednicer"], title: str = "Buffalo Wings Airfoil", include_parameters: bool = True, include_derivatives: bool = True, include_diagnostics: bool = True, indent: int = 2, sort_keys: bool = True, ) -> Path: """ Write a file from explicit ``xi`` surface samples. Parameters ---------- airfoil : Airfoil Airfoil to sample. path : str | os.PathLike[str] Destination file path for the exported output. lower : FloatInput Lower-surface ``xi`` values in ``[0, 1]`` ordered from leading edge to trailing edge. upper : FloatInput Upper-surface ``xi`` values in ``[0, 1]`` ordered from leading edge to trailing edge. file_format : {"json", "selig", "lednicer"} File format to emit. title : str, default="Buffalo Wings Airfoil" Title line written as the first non-ignored line of text-based coordinate files. include_parameters : bool, default=True Whether to include sampled ``xi``, surface-local arc length, and native curve parameter vectors when writing JSON. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature when writing JSON. include_diagnostics : bool, default=True Whether to include serialized structured diagnostics when writing JSON. indent : int, default=2 Indentation level passed to ``json.dumps`` when writing JSON. sort_keys : bool, default=True Whether to sort JSON mapping keys deterministically when writing JSON. Returns ------- pathlib.Path Resolved output path after writing the file. """ return write_airfoil_surface_samples( sample_airfoil_at_xi(airfoil, lower=lower, upper=upper), path=path, file_format=file_format, title=title, include_parameters=include_parameters, include_derivatives=include_derivatives, include_diagnostics=include_diagnostics, indent=indent, sort_keys=sort_keys, )
[docs] def write_sampled_airfoil_at_arc_length( airfoil: Airfoil, *, path: str | PathLike[str], lower: FloatInput, upper: FloatInput, file_format: Literal["json", "selig", "lednicer"], title: str = "Buffalo Wings Airfoil", include_parameters: bool = True, include_derivatives: bool = True, include_diagnostics: bool = True, indent: int = 2, sort_keys: bool = True, ) -> Path: """ Write a file from explicit arc-length surface samples. Parameters ---------- airfoil : Airfoil Airfoil to sample. path : str | os.PathLike[str] Destination file path for the exported output. lower : FloatInput Lower-surface arc lengths ordered from leading edge to trailing edge. upper : FloatInput Upper-surface arc lengths ordered from leading edge to trailing edge. file_format : {"json", "selig", "lednicer"} File format to emit. title : str, default="Buffalo Wings Airfoil" Title line written as the first non-ignored line of text-based coordinate files. include_parameters : bool, default=True Whether to include sampled ``xi``, surface-local arc length, and native curve parameter vectors when writing JSON. include_derivatives : bool, default=True Whether to include slope, finite-slope flags, and curvature when writing JSON. include_diagnostics : bool, default=True Whether to include serialized structured diagnostics when writing JSON. indent : int, default=2 Indentation level passed to ``json.dumps`` when writing JSON. sort_keys : bool, default=True Whether to sort JSON mapping keys deterministically when writing JSON. Returns ------- pathlib.Path Resolved output path after writing the file. """ return write_airfoil_surface_samples( sample_airfoil_at_arc_length(airfoil, lower=lower, upper=upper), path=path, file_format=file_format, title=title, include_parameters=include_parameters, include_derivatives=include_derivatives, include_diagnostics=include_diagnostics, indent=indent, sort_keys=sort_keys, )
def _float_vector_to_list(values: FloatArray) -> list[float]: """ Convert a one-dimensional float array to Python floats. Parameters ---------- values : numpy.ndarray One-dimensional float array to convert. Returns ------- list[float] Converted Python float values. """ return cast(list[float], values.tolist()) def _float_matrix_to_list(values: FloatArray) -> list[list[float]]: """ Convert a two-dimensional float array to nested Python floats. Parameters ---------- values : numpy.ndarray Two-dimensional float array to convert. Returns ------- list[list[float]] Converted nested Python float values. """ return cast(list[list[float]], values.tolist()) def _bool_vector_to_list(values: NDArray[np.bool_]) -> list[bool]: """ Convert a one-dimensional boolean array to Python booleans. Parameters ---------- values : numpy.ndarray One-dimensional boolean array to convert. Returns ------- list[bool] Converted Python boolean values. """ return cast(list[bool], values.tolist()) def _diagnostic_location_to_dict( location: DiagnosticLocation | None, ) -> dict[str, object] | None: """ Convert optional diagnostic location metadata to plain data. Parameters ---------- location : DiagnosticLocation | None Optional diagnostic location metadata. Returns ------- dict[str, object] | None Plain-data diagnostic location mapping, or ``None`` when the diagnostic has no attached location. """ if location is None: return None return { "object_path": location.object_path, "field_path": location.field_path, "geometry_region": location.geometry_region, } def _diagnostic_to_dict(diagnostic: Diagnostic) -> dict[str, object]: """ Convert one structured diagnostic to plain Python data. Parameters ---------- diagnostic : Diagnostic Structured diagnostic to serialize. Returns ------- dict[str, object] Plain-data diagnostic mapping. """ return { "severity": diagnostic.severity.value, "code": diagnostic.code, "message": diagnostic.message, "location": _diagnostic_location_to_dict(diagnostic.location), } def _diagnostic_report_to_dict( report: DiagnosticReport, ) -> dict[str, object]: """ Convert a diagnostic report to plain Python data. Parameters ---------- report : DiagnosticReport Structured diagnostic report to serialize. Returns ------- dict[str, object] Plain-data report containing diagnostic entries and summary severity flags. """ return { "entries": [_diagnostic_to_dict(entry) for entry in report.entries], "has_errors": report.has_errors, "has_warnings": report.has_warnings, "has_infos": report.has_infos, } def _airfoil_surface_samples_to_selig( samples: AirfoilSurfaceSamples, *, title: str, ) -> str: """ Convert sampled lower and upper surfaces to Selig text. Parameters ---------- samples : AirfoilSurfaceSamples Full downstream surface-sampling payload. title : str Title line written as the first non-ignored line of the file. Returns ------- str Selig-format coordinate text. """ upper_coordinates = np.flip(samples.upper.coordinates, axis=0) lower_coordinates = samples.lower.coordinates[1:] all_coordinates = np.vstack((upper_coordinates, lower_coordinates)) lines = [title] lines.extend(_format_coordinates(all_coordinates)) return "\n".join(lines) + "\n" def _airfoil_surface_samples_to_lednicer( samples: AirfoilSurfaceSamples, *, title: str, ) -> str: """ Convert sampled lower and upper surfaces to Lednicer text. Parameters ---------- samples : AirfoilSurfaceSamples Full downstream surface-sampling payload. title : str Title line written as the first non-ignored line of the file. Returns ------- str Lednicer-format coordinate text. """ upper_coordinates = samples.upper.coordinates lower_coordinates = samples.lower.coordinates lines = [ title, f"{upper_coordinates.shape[0]} {lower_coordinates.shape[0]}", *_format_coordinates(upper_coordinates), *_format_coordinates(lower_coordinates), ] return "\n".join(lines) + "\n" def _format_coordinates(values: FloatArray) -> list[str]: """ Format coordinate rows for text-based airfoil export. Parameters ---------- values : FloatArray ``(n, 2)`` array of sampled coordinates. Returns ------- list[str] Coordinate rows formatted as whitespace-separated ``x y`` pairs. """ return [f"{row[0]:.16g} {row[1]:.16g}" for row in values]