Skip to content

write_section_aero writes an all-NaN deflected .dat, and read_section_ #300

Description

@1-Bort-1

write_section_aero writes an all-NaN deflected .dat, and read_section_aero loads it as a NaN contour without complaint

When XFoil converges at no angle of attack for one deflection, generate_airfoil_aero leaves that whole contour column at its fill(NaN, …) initial value, write_section_aero writes it out as airfoils/<id>_d<tag>.dat full of NaN NaN rows, and read_section_aero then loads it back into the wing's contour interpolants without a word. The loaded Wing has a NaN airfoil shape at every deflection, including exactly 0°.

The chain

src/airfoil_aero/airfoil_solvers/xfoil_solver.jl:60-63 — a non-converged angle returns a SectionSolution with empty node arrays. (NeuralFoilSolver always fills s.x = def.x at neuralfoil_solver.jl:32-42, so this is XFoil-only.)

src/airfoil_aero/section_aero_gen.jl:27-39 — the contour matrices start as fill(NaN, n_node, n_delta) and a column is written only from a solution whose length(s.cp) == n_node, the modal node count over the whole grid. Every angle of that deflection hitting the continue on line 32 leaves x[:, jd] entirely NaN. A column is all-finite or all-NaN, never mixed. fill_node_nans! repairs only cp/cf, never x/y.

src/airfoil_aero/geometry_gen.jl:48-49generate_airfoils checks only column 1 (all(isnan, clvals) over sols[1]), so a deflection that converged nowhere does not stop the id being reported as solved.

src/airfoil_aero/section_aero_gen.jl:137-141write_section_aero writes every non-zero deflection unconditionally, before the all-NaN guard on :142-146 runs. That guard's own comment states the hazard the deflected files are not protected from:

    # never write `$dat_prefix.dat` all-NaN (read_dat_coordinates would drop it to empty)
    finite(jd) = any(isfinite, view(aero.x, :, jd))

The two readers disagree, and the lenient one is the dangerous one

read_dat_coordinates (src/airfoil_aero/airfoil_io.jl:12) skips any line whose first character is not a digit, - or ., so it returns zero points and the caller crashes.

read_dat (src/section_aero.jl:126-137) has no such filter, and tryparse(Float64, "NaN") succeeds, so read_section_aero gets a full-length NaN vector, the lengths match, x[:, jd] .= xd succeeds, and the NaN reaches x_interp/y_interp (section_aero.jl:82-85). Linear interpolation between a finite and a NaN node is NaN everywhere, so section_surface returns a NaN contour at every delta — 0.0 included — and hands it to live_polar.jl:136 and the Makie skin plot. No error, no warning.

How it surfaced

CI job Test end-user and developer setup, twice on the same commit (runs 34585324837), on examples/ram_air_kite.jl with delta_range = 0:1 and XFoilSolver. Wing(yaml) on line 74 loaded fine — through the lenient reader, with a NaN contour. Line 79's audit plot, which goes through the strict reader, is what actually failed:

LoadError: ArgumentError: reducing over an empty collection is not allowed
 [14] normalize_airfoil          @ src/airfoil_aero/kulfan.jl:71   (argmin on empty x)
 [15] fit_kulfan_parameters      @ src/airfoil_aero/kulfan.jl:136
 [17] generated_slices           @ ext/VortexStepMethodMakieExt.jl:1630
 [25] plot_slices_3d(path; delta=1.0, obj_path=...)

So the crash is the lucky case. The silent one is the default path for anyone loading a generated geometry.yaml.

Ten lines reproduce it with no solver at all — a SectionAero whose 1° column is NaN:

wrote: 7_d1.dat  isfile=true
NaN NaN
read back: 0 points
fit_kulfan_parameters threw: ArgumentError: reducing over an empty collection is not allowed

Reproducible on the GitHub runner, not on the agent box, where XFoil converges for all 8 solved sections of that mesh and every _d1.dat reads back 239 clean points. The setup-test workflow resolves dependencies fresh on each run (test/test_setup.sh:62-65) and the example, unlike ram_air_matrix_dir (test/test_data_utils.jl:51-52), does not pin BLAS threads — so which deflections converge moves between runs. Related: #291 on that fixture's instability.

What I would do

  1. Skip the column in write_section_aero's deflection loop when finite(jd) is false, hoisting the helper above the loop — one condition, and it reuses the guard already sitting three lines below. No all-NaN .dat is ever written, so isfile remains the honest test for every reader.
  2. Make read_dat and read_dat_coordinates agree on NaN rows. Two readers of one format with different tolerances is how the silent case got in; the strict one's behaviour is the one worth keeping.
  3. Decide whether generate_airfoils should fail an id outright when a deflection converged at no angle, the way it already does for column 1 (geometry_gen.jl:48-49). That is a behaviour question rather than a bug, and it is the one I would want an opinion on.

No test asserts a deflected .dat is re-readable; the one round-trip testset (test/airfoil_aero/test_airfoil_aero.jl:131) builds an all-finite contour and round-trips through the lenient reader, so it could not have caught this.

Related to #295, which names the same write_section_aero guard — but that issue is about a degenerate finite fit at 1e4 scale, not an all-NaN column, and neither fix covers the other.

Found while working #298 (#299), whose red Test end-user and developer setup check this is. That diff only changes which filename generated_slices asks for, and for the delta=1.0 this example uses the old and new schemes produce the identical _d1.dat — verified by evaluating both, and against the generated directory itself. Out of scope there.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions