Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,20 @@
and at flap deflection `delta`, in one figure instead of one coefficient per call.
- `linearize` takes a `BodyAerodynamics` with more than one wing; `theta_idxs` and
`delta_idxs` then run over the unrefined sections of all wings in order.
- `table_format=:arrow` writes the polars as `.arrow`, like the per-node `Cp`/`cf`
tables: `write_polar` and `write_polar_matrix` write Arrow for an `.arrow` path and
`load_polar_data` reads it. `resolve_aero_geometry` takes `table_format` too, for the
polars it generates. `obj_to_yaml` and `surfplan_to_aero_yaml` convert a reused
dataset's polars and node tables to `table_format`. A CSV polar loads as before
when every column is numeric; one with a non-numeric column warns and loads as
`INVISCID`.

### Changed

- BREAKING: `write_polar_csv` is `write_polar` and `write_polar_matrix_csv` is
`write_polar_matrix`. The geometry YAML names the polar `polar_file_path`; the old
key `csv_file_path` is still read, and `obj_to_yaml` rewrites it when it migrates a
dataset.
- BREAKING: artificial damping is removed: the `is_with_artificial_damping` and
`artificial_damping` keyword arguments of `Solver`, and the `artificial_damping`, `k2`
and `k4` solver settings. `k2` and `k4` had no effect; `artificial_damping: true`
Expand Down Expand Up @@ -74,7 +85,7 @@
points below and above `y = 0`, which on a cambered section put the hinge near the
chord line or on the wrong surface. Polars with a flap deflection change slightly.
It throws an `ArgumentError` for a contour that crosses that line fewer than twice.
- `write_polar_csv` for `SectionSolution`s, `write_polar_matrix_csv` and `write_aero_matrix`
- `write_polar` for `SectionSolution`s, `write_polar_matrix` and `write_aero_matrix`
write coefficients at 16 significant digits instead of 4 decimals, so a `POLAR_MATRICES`
table carries the drag response to a small flap deflection. Regenerate existing tables
to benefit.
Expand Down
40 changes: 20 additions & 20 deletions data/TUDELFT_V3_KITE/aero_geometry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ wing_airfoils:
# Average wind tunnel: 9 (standard "e^9 method")
# Dirty wind tunnel: 4–8
# - polars:
# csv_file_path: Path to polar CSV file (columns: alpha [rad], cl, cd, cm)
# polar_file_path: Path to polar CSV file (columns: alpha [rad], cl, cd, cm)
# - masure_regression:
# t, eta, kappa, delta, lamba, phi: Regression parameters
# - inviscid:
Expand All @@ -86,22 +86,22 @@ wing_airfoils:
# The polars were generated using 2D RANS CFD simulations at Re=5e5
# And fitted using PCHIP interpolation
# See --> https://repository.tudelft.nl/record/uuid:865d59fc-ccff-462e-9bac-e81725f1c0c9
- [1, polars, {csv_file_path: "polars_CFD/1.csv"}]
- [2, polars, {csv_file_path: "polars_CFD/2.csv"}]
- [3, polars, {csv_file_path: "polars_CFD/3.csv"}]
- [4, polars, {csv_file_path: "polars_CFD/4.csv"}]
- [5, polars, {csv_file_path: "polars_CFD/5.csv"}]
- [6, polars, {csv_file_path: "polars_CFD/6.csv"}]
- [7, polars, {csv_file_path: "polars_CFD/7.csv"}]
- [8, polars, {csv_file_path: "polars_CFD/8.csv"}]
- [9, polars, {csv_file_path: "polars_CFD/9.csv"}]
- [10, polars, {csv_file_path: "polars_CFD/10.csv"}]
- [11, polars, {csv_file_path: "polars_CFD/11.csv"}]
- [12, polars, {csv_file_path: "polars_CFD/12.csv"}]
- [13, polars, {csv_file_path: "polars_CFD/13.csv"}]
- [14, polars, {csv_file_path: "polars_CFD/14.csv"}]
- [15, polars, {csv_file_path: "polars_CFD/15.csv"}]
- [16, polars, {csv_file_path: "polars_CFD/16.csv"}]
- [17, polars, {csv_file_path: "polars_CFD/17.csv"}]
- [18, polars, {csv_file_path: "polars_CFD/18.csv"}]
- [19, polars, {csv_file_path: "polars_CFD/19.csv"}]
- [1, polars, {polar_file_path: "polars_CFD/1.csv"}]
- [2, polars, {polar_file_path: "polars_CFD/2.csv"}]
- [3, polars, {polar_file_path: "polars_CFD/3.csv"}]
- [4, polars, {polar_file_path: "polars_CFD/4.csv"}]
- [5, polars, {polar_file_path: "polars_CFD/5.csv"}]
- [6, polars, {polar_file_path: "polars_CFD/6.csv"}]
- [7, polars, {polar_file_path: "polars_CFD/7.csv"}]
- [8, polars, {polar_file_path: "polars_CFD/8.csv"}]
- [9, polars, {polar_file_path: "polars_CFD/9.csv"}]
- [10, polars, {polar_file_path: "polars_CFD/10.csv"}]
- [11, polars, {polar_file_path: "polars_CFD/11.csv"}]
- [12, polars, {polar_file_path: "polars_CFD/12.csv"}]
- [13, polars, {polar_file_path: "polars_CFD/13.csv"}]
- [14, polars, {polar_file_path: "polars_CFD/14.csv"}]
- [15, polars, {polar_file_path: "polars_CFD/15.csv"}]
- [16, polars, {polar_file_path: "polars_CFD/16.csv"}]
- [17, polars, {polar_file_path: "polars_CFD/17.csv"}]
- [18, polars, {polar_file_path: "polars_CFD/18.csv"}]
- [19, polars, {polar_file_path: "polars_CFD/19.csv"}]
40 changes: 20 additions & 20 deletions data/TUDELFT_V3_KITE/aero_geometry_coarse_discretisation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ wing_airfoils:
# Average wind tunnel: 9 (standard "e^9 method")
# Dirty wind tunnel: 4–8
# - polars:
# csv_file_path: Path to polar CSV file (columns: alpha [rad], cl, cd, cm)
# polar_file_path: Path to polar CSV file (columns: alpha [rad], cl, cd, cm)
# - measure_regression:
# t, eta, kappa, delta, lambda, phi: Regression parameters
# - inviscid:
Expand All @@ -150,22 +150,22 @@ wing_airfoils:
reynolds: !!float 1e6 # Reynolds number
headers: [airfoil_id, type, info_dict]
data:
- [1, polars, { csv_file_path: '2D_polars_drag_adjusted/1.csv' }]
- [2, polars, { csv_file_path: '2D_polars_drag_adjusted/2.csv' }]
- [3, polars, { csv_file_path: '2D_polars_drag_adjusted/3.csv' }]
- [4, polars, { csv_file_path: '2D_polars_drag_adjusted/4.csv' }]
- [5, polars, { csv_file_path: '2D_polars_drag_adjusted/5.csv' }]
- [6, polars, { csv_file_path: '2D_polars_drag_adjusted/6.csv' }]
- [7, polars, { csv_file_path: '2D_polars_drag_adjusted/7.csv' }]
- [8, polars, { csv_file_path: '2D_polars_drag_adjusted/8.csv' }]
- [9, polars, { csv_file_path: '2D_polars_drag_adjusted/9.csv' }]
- [10, polars, { csv_file_path: '2D_polars_drag_adjusted/10.csv' }]
- [11, polars, { csv_file_path: '2D_polars_drag_adjusted/11.csv' }]
- [12, polars, { csv_file_path: '2D_polars_drag_adjusted/12.csv' }]
- [13, polars, { csv_file_path: '2D_polars_drag_adjusted/13.csv' }]
- [14, polars, { csv_file_path: '2D_polars_drag_adjusted/14.csv' }]
- [15, polars, { csv_file_path: '2D_polars_drag_adjusted/15.csv' }]
- [16, polars, { csv_file_path: '2D_polars_drag_adjusted/16.csv' }]
- [17, polars, { csv_file_path: '2D_polars_drag_adjusted/17.csv' }]
- [18, polars, { csv_file_path: '2D_polars_drag_adjusted/18.csv' }]
- [19, polars, { csv_file_path: '2D_polars_drag_adjusted/19.csv' }]
- [1, polars, { polar_file_path: '2D_polars_drag_adjusted/1.csv' }]
- [2, polars, { polar_file_path: '2D_polars_drag_adjusted/2.csv' }]
- [3, polars, { polar_file_path: '2D_polars_drag_adjusted/3.csv' }]
- [4, polars, { polar_file_path: '2D_polars_drag_adjusted/4.csv' }]
- [5, polars, { polar_file_path: '2D_polars_drag_adjusted/5.csv' }]
- [6, polars, { polar_file_path: '2D_polars_drag_adjusted/6.csv' }]
- [7, polars, { polar_file_path: '2D_polars_drag_adjusted/7.csv' }]
- [8, polars, { polar_file_path: '2D_polars_drag_adjusted/8.csv' }]
- [9, polars, { polar_file_path: '2D_polars_drag_adjusted/9.csv' }]
- [10, polars, { polar_file_path: '2D_polars_drag_adjusted/10.csv' }]
- [11, polars, { polar_file_path: '2D_polars_drag_adjusted/11.csv' }]
- [12, polars, { polar_file_path: '2D_polars_drag_adjusted/12.csv' }]
- [13, polars, { polar_file_path: '2D_polars_drag_adjusted/13.csv' }]
- [14, polars, { polar_file_path: '2D_polars_drag_adjusted/14.csv' }]
- [15, polars, { polar_file_path: '2D_polars_drag_adjusted/15.csv' }]
- [16, polars, { polar_file_path: '2D_polars_drag_adjusted/16.csv' }]
- [17, polars, { polar_file_path: '2D_polars_drag_adjusted/17.csv' }]
- [18, polars, { polar_file_path: '2D_polars_drag_adjusted/18.csv' }]
- [19, polars, { polar_file_path: '2D_polars_drag_adjusted/19.csv' }]
2 changes: 1 addition & 1 deletion data/pyramid_model/wing_geometry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ wing_airfoils:
reynolds: 1e6
headers: [airfoil_id, type, info_dict]
data:
- [1, polars, {csv_file_path: "polars/V3_kite_midspan_airfoil_polar.csv"}]
- [1, polars, {polar_file_path: "polars/V3_kite_midspan_airfoil_polar.csv"}]

bridle_nodes:
# ---------------------------------------------------------------
Expand Down
23 changes: 13 additions & 10 deletions docs/src/airfoil_pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The conversion runs four stages per spanwise station:
└── XFoil: coordinates → panel code (± repanel)
│ 4. write
▼
airfoils/*.dat + polars/*.csv + pressure/* + geometry.yaml → Wing(geometry.yaml)
airfoils/*.dat + polars/* + pressure/* + geometry.yaml → Wing(geometry.yaml)
```

## 1. Slice
Expand Down Expand Up @@ -97,21 +97,24 @@ For each unique airfoil id `j`, `obj_to_yaml` writes into `output_dir`:
- `airfoils/{j}_d{tag}.dat` — each deflected shape, when a `delta_range` is given. The
`{tag}` encodes the deflection in degrees (`m` for a minus sign, `p` for the decimal
point), e.g. `_d5.dat` for 5°, `_dm3.dat` for −3°, `_d2p5.dat` for 2.5°
- `polars/{j}.csv` — the generated polar (`POLAR_VECTORS` or `POLAR_MATRICES`)
- `pressure/{j}_cp.csv`, `pressure/{j}_cf.csv` — surface pressure and skin friction per
contour node over the `alpha × delta` grid, what pressure integration reads.
`table_format=:arrow` writes these two as `.arrow` instead: binary, roughly an order of
magnitude faster to load and a third of the size. `read_section_aero` detects the format
from the suffix, so both are loadable and a directory can be migrated between them with
[`convert_node_table`](@ref VortexStepMethod.convert_node_table) — which `obj_to_yaml`
does itself when `table_format` differs from what the directory holds, without re-running
the solver
- `polars/{j}.{table_format}` — the generated polar (`POLAR_VECTORS` or `POLAR_MATRICES`),
referenced from the YAML as `polar_file_path`
- `pressure/{j}_cp.{table_format}`, `pressure/{j}_cf.{table_format}` — surface pressure
and skin friction per contour node over the `alpha × delta` grid, what pressure
integration reads
- `geometry.yaml` — `wing_sections` (leading/trailing-edge points) plus `wing_airfoils`
(each section's `type` and the table paths above). `geometry_path` writes the YAML
somewhere else, in which case the emitted table references carry the path from the YAML's
directory to `output_dir` — the geometry loader resolves them against the YAML's own
directory

`table_format=:arrow` writes the polar and the two node tables as `.arrow` instead: binary, roughly an
order of magnitude faster to load and a third of the size. The loaders detect the format
from the suffix, so both are loadable and a directory can be migrated between them with
[`convert_node_table`](@ref VortexStepMethod.convert_node_table) — which `obj_to_yaml` and
`surfplan_to_aero_yaml` do themselves when `table_format` differs from what the directory
holds, without re-running the solver.

A tip that tapers to a point has no airfoil to slice, so the outermost stations stop at
the last slice that still has a chord; `wingtip_distance` moves them a further spanwise
length inboard when the slices just short of the tip are still too thin to analyse. A
Expand Down
9 changes: 6 additions & 3 deletions docs/src/private_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,15 @@ same_knots
polar_knots
window_alpha
assemble_polar_matrix
load_polar_data
load_matrix_polar_data
read_aero_matrix
read_dat_coordinates
read_node_table
read_arrow_columns
read_csv_columns
write_node_rows
csv_fields
convert_node_table
delta_suffix
interpolate_matrix_nans!
Expand Down Expand Up @@ -214,9 +218,8 @@ create_2d_polars
lei_poly_coeffs
resolve_airfoil
write_dat
csv_fields
write_polar_csv
write_polar_matrix_csv
write_polar
write_polar_matrix
write_aero_matrix
write_node_table
flat_plate_cf
Expand Down
1 change: 1 addition & 0 deletions src/VortexStepMethod.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ using SciMLBase
import NonlinearSolve: solve, solve!
using Interpolations
using Parameters
using Printf: @sprintf
using Serialization
using Timers
using PreallocationTools
Expand Down
4 changes: 2 additions & 2 deletions src/airfoil_aero/AirfoilAero.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ using NPZ
using Xfoil
using Printf: @sprintf
using ..VortexStepMethod: SectionAero, interpolate_matrix_nans!, delta_suffix,
write_node_rows, section_surface, set_polar!,
write_node_rows, csv_fields, section_surface, set_polar!,
read_dat_coordinates, KulfanParameters, calculate_cl,
calculate_cd, calculate_cm

Expand Down Expand Up @@ -44,7 +44,7 @@ export SectionSolution, DeformedSection, deform_section, analyze_section, analyz
export create_2d_polars, generate_aero_matrices, generate_section_aero, lei_poly_coeffs
export generate_airfoil_aero, write_section_aero
export turn_trailing_edge!, get_lower_upper
export read_dat_coordinates, write_dat, write_polar_csv, write_polar_matrix_csv
export read_dat_coordinates, write_dat, write_polar, write_polar_matrix
export generate_polar_from_coordinates, generate_polar_from_dat, resolve_airfoil
export write_geometry_yaml, write_yaml, generate_airfoils

Expand Down
77 changes: 34 additions & 43 deletions src/airfoil_aero/airfoil_io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,35 @@ function write_dat(filepath::String, name::String, x::Vector, y::Vector)
end

"""
csv_fields(values) -> String
write_polar(filepath, result::NeuralFoilResult)

Join `values` into one comma-separated CSV line at 16 significant digits.
Write a NeuralFoil result to a `POLAR_VECTORS` table (`alpha, Cd, Cs, Cl, Cm`), CSV or
Arrow as the suffix of `filepath` says (see [`write_node_rows`](@ref
VortexStepMethod.write_node_rows)).
"""
csv_fields(values) = join((@sprintf("%.16g", v) for v in values), ",")

"""
write_polar_csv(filepath, result::NeuralFoilResult)

Write a NeuralFoil result to a `POLAR_VECTORS` CSV (`alpha, Cd, Cs, Cl, Cm`).
"""
function write_polar_csv(filepath::String, result::NeuralFoilResult)
n = length(result.alpha)
open(filepath, "w") do io
println(io, "alpha,Cd,Cs,Cl,Cm")
for i in 1:n
row = (result.alpha[i], result.CD[i], 0.0, result.CL[i], result.CM[i])
println(io, csv_fields(row))
end
end
return filepath
function write_polar(filepath::String, result::NeuralFoilResult)
values = [result.CD zero(result.CD) result.CL result.CM]
return write_node_rows(filepath, deg2rad.(result.alpha), nothing, values;
columns=["Cd", "Cs", "Cl", "Cm"])
end

"""
generate_polar_from_coordinates(x, y, output_path; Re, alpha_range=-180:1:180,
solver=NeuralFoilSolver(), delta_range=nothing,
crease_frac=0.75, dat_prefix=nothing)

Sweep `solver` over the airfoil coordinates `(x, y)` and write the polar CSV. XFoil
uses the coordinates directly; NeuralFoil fits [`LeastSquaresFit`](@ref) Kulfan
parameters ([`deform_section`](@ref)). Wrap a raw or open single-membrane slice with
[`shrink_wrap`](@ref) before calling this. Pass a [`NeuralFoilSolver`](@ref) or
[`XFoilSolver`](@ref) to pick the backend. With `delta_range === nothing` the sweep is
over `alpha_range` only and written as a `POLAR_VECTORS` CSV (returns the
`Vector{SectionSolution}`); pass a `delta_range` of trailing-edge deflections to sweep
`(alpha, delta)` and write a long-format `POLAR_MATRICES` CSV (returns the `(cl, cd,
cm)` matrices). Both angle ranges are in degrees. `crease_frac` is the chordwise hinge
location (0–1) about which each `delta_range` deflection pivots. With `dat_prefix` set,
each deflected shape is also written to `{dat_prefix}_{delta_suffix(δ)}.dat`.
Sweep `solver` over the airfoil coordinates `(x, y)` and write the polar table, CSV or
Arrow as the suffix of `output_path` says. XFoil uses the coordinates directly;
NeuralFoil fits [`LeastSquaresFit`](@ref) Kulfan parameters ([`deform_section`](@ref)).
Wrap a raw or open single-membrane slice with [`shrink_wrap`](@ref) before calling this.
Pass a [`NeuralFoilSolver`](@ref) or [`XFoilSolver`](@ref) to pick the backend. With
`delta_range === nothing` the sweep is over `alpha_range` only and written as a
`POLAR_VECTORS` table (returns the `Vector{SectionSolution}`); pass a `delta_range` of
trailing-edge deflections to sweep `(alpha, delta)` and write a long-format
`POLAR_MATRICES` table (returns the `(cl, cd, cm)` matrices). Both angle ranges are in
degrees. `crease_frac` is the chordwise hinge location (0–1) about which each
`delta_range` deflection pivots. With `dat_prefix` set, each deflected shape is also
written to `{dat_prefix}_{delta_suffix(δ)}.dat`.
"""
function generate_polar_from_coordinates(x::Vector, y::Vector, output_path::String;
Re::Real, alpha_range=-180:1:180,
Expand All @@ -63,7 +53,7 @@ function generate_polar_from_coordinates(x::Vector, y::Vector, output_path::Stri
if delta_range === nothing
def = deform_section(x, y, 0.0)
sols = analyze_sweep(solver, def, alphas, Re)
write_polar_csv(output_path, sols)
write_polar(output_path, sols)
return sols
end
deltas = deg2rad.(collect(Float64, delta_range))
Expand All @@ -72,33 +62,35 @@ function generate_polar_from_coordinates(x::Vector, y::Vector, output_path::Stri
"deflection", xd, yd)
cl, cd, cm = generate_aero_matrices(solver, x, y;
alpha_range=alphas, delta_range=deltas, Re, crease_frac, on_deform)
write_polar_matrix_csv(output_path, alphas, deltas, cl, cd, cm)
write_polar_matrix(output_path, alphas, deltas, cl, cd, cm)
return (cl, cd, cm)
end

"""
resolve_airfoil(type, info, out_dir, id; Re, alpha_range) -> (new_type, new_info)
resolve_airfoil(type, info, out_dir, id; Re, alpha_range, table_format=:csv)
-> (new_type, new_info)

Resolve one awesIO `wing_airfoils` entry to a core-loadable form. `breukels_regression`
`(t, kappa)` → `poly` coeffs (via [`lei_poly_coeffs`](@ref)); `neuralfoil`
`(dat_file_path, …)` → a `polars` CSV (via [`generate_polar_from_dat`](@ref)) written
under `out_dir`; `polars`/`poly`/`inviscid` pass through. `masure_regression` is not
yet supported. `info` file paths should already be absolute.
`(dat_file_path, …)` → a `polars` table `{id}.{table_format}` (`:csv` or `:arrow`, via
[`generate_polar_from_dat`](@ref)) written under `out_dir`; `polars`/`poly`/`inviscid`
pass through. `masure_regression` is not yet supported. `info` file paths should
already be absolute.
"""
function resolve_airfoil(type::AbstractString, info::AbstractDict, out_dir, id;
Re, alpha_range)
Re, alpha_range, table_format::Symbol=:csv)
if type == "breukels_regression"
cl, cd, cm = lei_poly_coeffs(Float64(info["t"]), Float64(info["kappa"]))
return "poly", Dict{String,Any}("cl_coeffs" => cl, "cd_coeffs" => cd,
"cm_coeffs" => cm)
elseif type == "neuralfoil"
csv = joinpath(out_dir, "$(id).csv")
polar = joinpath(out_dir, "$(id).$(table_format)")
solver = NeuralFoilSolver(
model_size=String(get(info, "model_size", "large")),
n_crit=Float64(get(info, "n_crit", 9.0)))
generate_polar_from_dat(String(info["dat_file_path"]), csv; Re,
generate_polar_from_dat(String(info["dat_file_path"]), polar; Re,
alpha_range=collect(alpha_range), solver)
return "polars", Dict{String,Any}("csv_file_path" => csv)
return "polars", Dict{String,Any}("polar_file_path" => polar)
elseif type in ("polars", "poly", "inviscid")
return String(type), info
elseif type == "masure_regression"
Expand All @@ -111,9 +103,8 @@ end
"""
generate_polar_from_dat(dat_path, output_path; Re, kwargs...)

Read a `.dat` airfoil and generate its polar CSV via
`generate_polar_from_coordinates`. This is AirfoilAero's `.dat` → CSV entry
point.
Read a `.dat` airfoil and generate its polar table via
`generate_polar_from_coordinates`. This is AirfoilAero's `.dat` → polar entry point.
"""
function generate_polar_from_dat(dat_path::String, output_path::String;
Re::Real, kwargs...)
Expand Down
Loading
Loading