Airfoil Surface Sampling API

This document defines the public downstream surface-sampling API exposed through buffalo_wings.airfoil.sample_airfoil(...), sample_airfoil_at_xi(...), and sample_airfoil_at_arc_length(...). It also defines the public export helpers layered on top of those typed sampling results. It is intended to make Buffalo Wings the source of truth for deterministic airfoil geometry handoff to Buffalo Panel and future Buffalo Mesh workflows.

Scope

This contract covers sampled airfoil geometry and metadata needed before any downstream fitting or meshing step begins. It also covers the export helpers that turn a typed sampling result into plain Python containers, JSON strings, JSON files, or coordinate files. It does not define fitted-curve generation, fitting tolerances, meshing topology, or fit-quality diagnostics. It does define the structured diagnostics emitted while preparing a sampling payload.

Public Entry Points

Use sample_airfoil(...) for standard built-in sampling. Use sample_airfoil_at_xi(...) when the caller already has explicit surface-local coordinates. Use sample_airfoil_at_arc_length(...) when the caller already has explicit surface-local arc lengths. All three functions return an AirfoilSurfaceSamples object with typed lower-surface and upper-surface payloads. Each returned AirfoilSurfaceSamples object also carries a diagnostics report built from the shared buffalo_core.diagnostics types DiagnosticSeverity, DiagnosticLocation, Diagnostic, and DiagnosticReport.

Use sample_airfoil_result(...), sample_airfoil_at_xi_result(...), or sample_airfoil_at_arc_length_result(...) when the sampling workflow should also return a shared buffalo_core.diagnostics.OperationResult[AirfoilSurfaceSamples] wrapper. Those result-returning entry points also expose warning_policy so the standard slope-singularity advisory can be handled as a runtime warning, ignored, promoted to an exception, or left diagnostics-only.

Use surface_samples_to_dict(...) or airfoil_surface_samples_to_dict(...) when an existing typed sampling result should be exported as plain Python data. Use airfoil_surface_samples_to_json(...) when an existing typed sampling result should be serialized as JSON text. Use write_airfoil_surface_samples(...) when an existing typed sampling result should be written as a file, with file_format="json", file_format="selig", or file_format="lednicer". Use write_sampled_airfoil(...), write_sampled_airfoil_at_xi(...), or write_sampled_airfoil_at_arc_length(...) when the one-step workflow should write files directly, with file_format="json", file_format="selig", or file_format="lednicer". The public surface therefore has four related layers:

  1. typed sampling results via SurfaceSamples and AirfoilSurfaceSamples

  2. diagnostics-aware sampling results via buffalo_core.diagnostics.OperationResult[AirfoilSurfaceSamples]

  3. two-step export via surface_samples_to_dict(...), airfoil_surface_samples_to_dict(...), airfoil_surface_samples_to_json(...), and write_airfoil_surface_samples(...)

  4. one-step file export via the write_* (...) convenience wrappers

Use sample_airfoil_boundary(...) when a downstream tool needs one ordered boundary point distribution rather than separate upper- and lower-surface payloads. This helper returns AirfoilBoundarySamples, which always includes coordinates, surface membership, upper/lower index metadata, leading-edge index metadata, and a trailing-edge closure flag. Optional boundary quantities are requested by name through quantities, currently "arc_length", "tangent", "normal", and "curvature". The boundary helper preserves the trailing-edge geometry implied by the source airfoil and does not force closed geometry.

The typed dataclasses remain the canonical in-memory contract. The plain-data helpers are an export view layered on top of that contract rather than a replacement for it. The shared buffalo_core.diagnostics.OperationResult[...] wrapper is the workflow contract for callers that want typed geometry plus explicit structured diagnostics behavior at the API boundary.

Export Payload Controls

The dict and JSON export helpers preserve coordinates, surface lengths, leading-edge and trailing-edge metadata, and chord length by default. They also include sampled parameters, derivative fields, and diagnostics by default so the exported payload mirrors the full typed sampling result. Callers may trim optional sections with the inclusion flags:

  • include_parameters

  • include_derivatives

  • include_diagnostics

include_parameters controls whether xi, surface-local arc_length, and native curve_u are exported. include_derivatives controls whether slope, slope_is_finite, and curvature are exported. include_diagnostics controls whether the structured diagnostics report is included in the top-level exported airfoil payload. These controls exist so downstream handoff can stay lightweight without changing the typed sampling contract.

Coordinate-File Export

The file writers currently support json, selig, and lednicer. For selig, the exported points run from the upper trailing-edge endpoint to the leading edge and then back to the lower trailing-edge endpoint, with the leading-edge point written once. For lednicer, the exported file contains a title line, a declared upper/lower point-count line, and then separate upper and lower surface blocks, both ordered from leading edge to trailing edge. These writers are layered on top of the typed sampling contract rather than separate geometry-generation paths.

Coordinate Frame And Normalization

All coordinates are returned in the normalized local airfoil section frame. The leading edge is at x = 0. The nominal trailing edge is at x = 1. Positive y is the upper-surface side. The normalization field is fixed to unit_chord_local_frame. The chord_length field reports the actual runtime chord implied by the airfoil object.

Surface Ordering

Both sampled surfaces are ordered from leading edge to trailing edge. The top-level coordinate_ordering field is fixed to surface_leading_edge_to_trailing_edge. The first point on both lower.coordinates and upper.coordinates is the leading-edge point. The last point on lower.coordinates is the lower trailing-edge point. The last point on upper.coordinates is the upper trailing-edge point. Surface ordering is defined by the returned sample arrays and not by monotonic x progression alone.

Boundary Ordering

Boundary samples are ordered according to the requested order. The first supported order is lower_to_upper, which starts at the lower trailing edge, proceeds along the lower surface to the leading edge, and then proceeds along the upper surface to the upper trailing edge. The leading-edge point is included once. Distinct upper and lower trailing-edge points are preserved when the airfoil has a finite trailing-edge gap. The trailing_edge_is_closed flag reports whether the first and last boundary points coincide within the runtime root tolerance. Downstream consumers are responsible for deciding whether an open trailing edge is acceptable, whether to add a closure panel, or whether to reject the geometry.

Parameter Ordering

Each surface payload includes a normalized xi in [0, 1]. xi = 0 corresponds to the leading edge. xi = 1 corresponds to the surface trailing edge. The top-level parameter_ordering field is fixed to xi_0_to_1.

Each surface payload also includes the signed airfoil curve parameter curve_u. For the upper surface, curve_u increases from 0 to 1. For the lower surface, curve_u decreases from 0 to -1. This preserves the runtime airfoil curve parameterization while still giving downstream code a surface-local LE-to-TE ordering. The public xi is therefore the downstream-facing surface coordinate, while curve_u remains the smooth internal runtime coordinate. The shared leading-edge point curve_u = 0 maps onto the public upper-surface branch so the public conversion helpers stay single-valued there.

Slope And Curvature

Each surface payload includes slope with shape (n,). slope is the section-frame quantity dy/dx. The contract intentionally returns slope rather than parameterization-dependent derivative vectors. Each surface payload also includes slope_is_finite with shape (n,). slope_is_finite marks which sampled slope values are finite. If a sampled point lies at a vertical tangent, slope may be inf, -inf, or nan. This is expected behavior rather than a geometry failure. The public sampling entry points emit an advisory warning because sampled surfaces can contain singular slope locations. That singular slope commonly appears near the leading edge. For some analytic, cambered airfoils, the upper-surface singular slope can occur at an x location less than 0. The same advisory is also emitted through the structured diagnostics report with warning severity and leading-edge context so GUI, CLI, and downstream code can inspect it programmatically.

Each surface payload includes signed curvature with shape (n,). Curvature sign is aligned with the returned sample ordering. If downstream code compares lower-surface curvature against the native Airfoil.k(...) result, the sign will be reversed because the native lower-surface t direction runs from trailing edge to leading edge.

Arc-Length Metadata

Each surface payload includes arc_length with shape (n,). arc_length measures distance along that surface from the leading edge to the sampled point. Each surface payload also reports the total surface_length from leading edge to trailing edge. The top-level arc_length_ordering field is fixed to surface_arc_length_0_to_length.

Trailing-Edge Handling

The top-level trailing_edge_handling field is fixed to preserve_surface_points. The contract preserves distinct trailing_edge_upper and trailing_edge_lower points. This keeps finite trailing-edge gaps explicit instead of collapsing them during export. The top-level trailing_edge_midpoint field is also provided for workflows that need the averaged trailing-edge location returned by Airfoil.trailing_edge().

Sampling Distribution

The built-in sample_airfoil(...) contract currently supports uniform and cosine spacing. uniform uses evenly spaced xi values. cosine uses half-cosine clustering near both the leading edge and trailing edge. The explicit sample_airfoil_at_xi(...) and sample_airfoil_at_arc_length(...) entry points let adaptive fitters and meshers choose their own sampling locations while still reusing the same payload format. Repeated explicit samples are preserved as provided and are not deduplicated by the API.

Determinism

The returned arrays are float64. For the same airfoil state and sampling inputs, the sampling output is deterministic. The contract is intended to be stable enough that downstream fitting and meshing preparation does not need to recreate local ordering, local parameter mapping, or lower-surface curvature-sign rules.

Diagnostics And Exceptions

Structured diagnostics supplement exceptions rather than replacing them. Sampling functions still raise ValueError for invalid inputs such as unsupported spacing modes, out-of-range surface coordinates, or decreasing explicit sample sequences. The diagnostics report is reserved for advisory and displayable workflow information that downstream tools may want to surface without interrupting execution. Current sampling diagnostics therefore report the slope-singularity advisory while the existing input-validation failures remain exception-first.