Skip to content

Commit 1ba7681

Browse files
committed
#185 Create rectangular wing from yaml and plot, using csv polar input
1 parent f9c7c80 commit 1ba7681

File tree

6 files changed

+353
-6
lines changed

6 files changed

+353
-6
lines changed

data/pyramid_model/polars/1.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
alpha,cl,cd,cm
2+
-10.0,0.1,0.01,0.0
3+
-5.0,0.5,0.012,0.02
4+
0.0,0.8,0.015,0.04
5+
5.0,1.1,0.018,0.06

data/pyramid_model/polars/2.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
alpha,cl,cd,cm
2+
-10.0,0.1,0.01,0.0
3+
-5.0,0.5,0.012,0.02
4+
0.0,0.8,0.015,0.04
5+
5.0,1.1,0.018,0.06

data/pyramid_model/pyramid.yaml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
wing_sections:
2+
# ---------------------------------------------------------------
3+
# headers:
4+
# - airfoil_id: integer, unique identifier for the airfoil (matches wing_airfoils)
5+
# - LE_x: x-coordinate of leading edge
6+
# - LE_y: y-coordinate of leading edge
7+
# - LE_z: z-coordinate of leading edge
8+
# - TE_x: x-coordinate of trailing edge
9+
# - TE_y: y-coordinate of trailing edge
10+
# - TE_z: z-coordinate of trailing edge
11+
# ---------------------------------------------------------------
12+
headers: [airfoil_id, LE_x, LE_y, LE_z, TE_x, TE_y, TE_z]
13+
data:
14+
- [1, 0, 10, 0, 1, 10, 0]
15+
- [2, 0, -10, 0, 1, -10, 0]
16+
17+
wing_airfoils:
18+
# ---------------------------------------------------------------
19+
# headers:
20+
# - airfoil_id: integer, unique identifier for the airfoil
21+
# - type: one of [neuralfoil, breukels_regression, masure_regression, polars]
22+
# - info_dict: dictionary with parameters depending on 'type'
23+
#
24+
# info_dict fields by type:
25+
# - breukels_regression:
26+
# t: Tube diameter non-dimensionalized by chord (required)
27+
# kappa: Maximum camber height/magnitude, non-dimensionalized by chord (required)
28+
# - neuralfoil:
29+
# dat_file_path: Path to airfoil .dat file (x, y columns)
30+
# model_size: NeuralFoil model size (e.g., "xxxlarge")
31+
# xtr_lower: Lower transition location (0=forced, 1=free)
32+
# xtr_upper: Upper transition location
33+
# n_crit: Critical amplification factor (see guidelines below)
34+
# n_crit guidelines:
35+
# Sailplane: 12–14
36+
# Motorglider: 11–13
37+
# Clean wind tunnel: 10–12
38+
# Average wind tunnel: 9 (standard "e^9 method")
39+
# Dirty wind tunnel: 4–8
40+
# - polars:
41+
# csv_file_path: Path to polar CSV file (columns: alpha [rad], cl, cd, cm)
42+
# - masure_regression:
43+
# t, eta, kappa, delta, lamba, phi: Regression parameters
44+
# ---------------------------------------------------------------
45+
alpha_range: [-10, 31, 0.5] # [deg], in this range the polars are calculated
46+
reynolds: !!float 1e6 # Reynolds number
47+
headers: [airfoil_id, type, info_dict]
48+
data:
49+
- [1, breukels_regression, {t: 0.3225132246121334, kappa: 0.0405837}]
50+
- [2, breukels_regression, {t: 0.1837401335882308, kappa: 0.043495}]

examples/menu.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ using REPL.TerminalMenus
99

1010
url = "https://opensourceawe.github.io/VortexStepMethod.jl/dev"
1111

12-
options = ["rectangular_wing = include(\"rectangular_wing.jl\")",
12+
options = [
13+
"pyramid_model = include(\"pyramid_model.jl\")",
14+
"rectangular_wing = include(\"rectangular_wing.jl\")",
1315
"ram_air_kite = include(\"ram_air_kite.jl\")",
1416
"stall_model = include(\"stall_model.jl\")",
1517
"bench = include(\"bench.jl\")",

examples/pyramid_model.jl

Lines changed: 142 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,146 @@
1-
#=
1+
using LinearAlgebra
2+
using VortexStepMethod
3+
using ControlPlots
4+
using YAML
5+
using CSV
6+
using DataFrames
27

3-
Example script to run the VortexStepMethod on a pyramid model.
48

5-
Second commit
9+
function read_yaml(yaml_file_path)
10+
# Read the YAML file to get the sections
11+
yaml_data = YAML.load_file(yaml_file_path)
12+
13+
# Extract headers and data
14+
headers = yaml_data["wing_sections"]["headers"]
15+
data_rows = yaml_data["wing_sections"]["data"]
16+
17+
# Convert YAML data to the expected format
18+
sections = []
19+
for row in data_rows
20+
# Create a dictionary mapping headers to values
21+
section_dict = Dict(zip(headers, row))
22+
23+
# Keep airfoil_id as integer for CSV filename lookup
24+
airfoil_id = section_dict["airfoil_id"]
25+
26+
# Extract coordinates
27+
le_coord = [section_dict["LE_x"], section_dict["LE_y"], section_dict["LE_z"]]
28+
te_coord = [section_dict["TE_x"], section_dict["TE_y"], section_dict["TE_z"]]
29+
30+
section = (
31+
airfoil_id = airfoil_id,
32+
le_coord = le_coord,
33+
te_coord = te_coord
34+
)
35+
push!(sections, section)
36+
end
637

7-
Third test commit
38+
return sections
39+
end
840

9-
=#
41+
function load_polar_data(airfoil_id, polars_dir)
42+
"""
43+
Load polar data from CSV file for given airfoil_id.
44+
Expected CSV format: columns named alpha, cl, cd, cm
45+
"""
46+
csv_filename = "$(airfoil_id).csv"
47+
csv_filepath = joinpath(polars_dir, csv_filename)
48+
49+
if !isfile(csv_filepath)
50+
@warn "Polar file not found: $csv_filepath. Using INVISCID model instead."
51+
return nothing, INVISCID
52+
end
53+
54+
try
55+
df = CSV.read(csv_filepath, DataFrame)
56+
57+
# Verify required columns exist
58+
required_cols = ["alpha", "cl", "cd", "cm"]
59+
missing_cols = setdiff(required_cols, names(df))
60+
if !isempty(missing_cols)
61+
@warn "Missing columns $missing_cols in $csv_filepath. Using INVISCID model instead."
62+
return nothing, INVISCID
63+
end
64+
65+
# Create aero_data tuple
66+
aero_data = (df.alpha, df.cl, df.cd, df.cm)
67+
return aero_data, POLAR_VECTORS
68+
69+
catch e
70+
@warn "Error reading polar file $csv_filepath: $e. Using INVISCID model instead."
71+
return nothing, INVISCID
72+
end
73+
end
74+
75+
76+
function instantiate_from_yaml(yaml_file_path, polars_dir; n_panels=20, spanwise_distribution=LINEAR)
77+
"""
78+
Create a wing geometry from YAML file with polar data loaded from CSV files.
79+
80+
# Arguments
81+
- `yaml_file_path`: Path to YAML file containing wing section definitions
82+
- `polars_dir`: Path to directory containing polar CSV files (named <airfoil_id>.csv)
83+
- `n_panels`: Number of panels for the wing (default: 20)
84+
- `spanwise_distribution`: Panel distribution type (default: LINEAR)
85+
86+
# Returns
87+
- `BodyAerodynamics`: Initialized aerodynamics object containing the wing
88+
89+
# Example
90+
```julia
91+
yaml_path = "pyramid.yaml"
92+
polars_dir = "/path/to/polars"
93+
body_aero = instantiate_from_yaml(yaml_path, polars_dir; n_panels=30)
94+
```
95+
"""
96+
# Create wing with specified panel distribution
97+
wing = Wing(n_panels, spanwise_distribution=spanwise_distribution)
98+
99+
# Read sections from YAML file
100+
sections = read_yaml(yaml_file_path)
101+
102+
# Add each section to the wing
103+
for section in sections
104+
# Load polar data for this airfoil
105+
aero_data, aero_model = load_polar_data(section.airfoil_id, polars_dir)
106+
107+
println("Section airfoil_id $(section.airfoil_id): Using $(aero_model) model")
108+
109+
add_section!(wing,
110+
section.le_coord, # Leading edge coordinate
111+
section.te_coord, # Trailing edge coordinate
112+
aero_model, # Aerodynamic model (POLAR_VECTORS or INVISCID)
113+
aero_data) # Aerodynamic data tuple or nothing
114+
end
115+
116+
# Initialize and return aerodynamics object
117+
body_aero = BodyAerodynamics([wing])
118+
return body_aero
119+
end
120+
121+
# getting the project_dir
122+
project_dir = dirname(dirname(pathof(VortexStepMethod))) # Go up one level from src to project root
123+
yaml_file_path = joinpath(project_dir, "data", "pyramid_model", "pyramid.yaml")
124+
polars_dir = joinpath(project_dir, "data", "pyramid_model", "polars")
125+
126+
sections = read_yaml(yaml_file_path)
127+
println("Read sections from YAML file: $(length(sections)) sections")
128+
println("Sections: $(sections)")
129+
130+
body_aero = instantiate_from_yaml(yaml_file_path, polars_dir; n_panels=20)
131+
println("Created wing geometry with $(length(body_aero.panels)) panels")
132+
133+
# Plotting Geometrys
134+
PLOT = true
135+
USE_TEX = false
136+
PLOT && plot_geometry(
137+
body_aero,
138+
"";
139+
data_type=".svg",
140+
save_path="",
141+
is_save=false,
142+
is_show=true,
143+
view_elevation=15,
144+
view_azimuth=-120,
145+
use_tex=USE_TEX
146+
)

examples/ram_air_kite_v2.jl

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using VortexStepMethod,LinearAlgebra
2+
using ControlPlots # Use plotx instead of Plots
3+
4+
# --- User-specified parameters ---
5+
wind_speed = 10.0 # [m/s]
6+
angle_of_attack_deg = 5.0 # [deg]
7+
sideslip_deg = 0.0
8+
yaw_rate = 0.0 # [rad/s] (not used in static analysis)
9+
air_density = 1.225 # [kg/m^3] Air density at sea level
10+
11+
println("=== Static Aerodynamic Analysis (VSM) ===")
12+
println("Wind speed: $wind_speed m/s")
13+
println("Angle of attack: $angle_of_attack_deg deg")
14+
println("Sideslip: $sideslip_deg deg")
15+
16+
# --- Setup kite and aero model ---
17+
ram_wing = RamAirWing("data/ram_air_kite_body.obj", "data/ram_air_kite_foil.dat"; align_to_principal=true)
18+
body_aero = BodyAerodynamics([ram_wing])
19+
20+
# --- Set apparent wind vector in body axes ---
21+
α = deg2rad(angle_of_attack_deg)
22+
β = deg2rad(sideslip_deg)
23+
va = wind_speed * [
24+
cos(α)*cos(β), # X_b (forward)
25+
sin(β), # Y_b (right)
26+
sin(α)*cos(β) # Z_b (down)
27+
]
28+
set_va!(body_aero, va)
29+
30+
# --- Create and run VSM solver ---
31+
solver = Solver(body_aero;
32+
aerodynamic_model_type=VSM,
33+
is_with_artificial_damping=false,
34+
rtol=1e-5,
35+
solver_type=NONLIN
36+
)
37+
38+
# One can run the solver seperately, given back a Dict{String, Any}
39+
solver = Solver(body_aero; density=air_density)
40+
solve!(solver, body_aero)
41+
forces = solver.sol.force
42+
println("\nType of solver: ", typeof(solver), " Type of forces: ", typeof(forces))
43+
44+
# Better to use the VortexStepMethod.solve method, returns a dictionary with the results
45+
results = VortexStepMethod.solve(solver, body_aero; log=true)
46+
println("Type of results: ", typeof(results))
47+
48+
49+
# --- Run over an alpha sweep ---
50+
# using Plots # Add this at the top if you want to plot
51+
alphas = 1:10
52+
f_xs = Float64[] # Fx over alpha sweep
53+
f_ys = Float64[] # Fy over alpha sweep
54+
f_zs = Float64[] # Fz over alpha sweep
55+
m_xs = Float64[] # Mx over alpha sweep
56+
m_ys = Float64[] # My over alpha sweep
57+
m_zs = Float64[] # Mz over alpha sweep
58+
59+
for alpha in alphas
60+
local_α = deg2rad(alpha)
61+
local_β = deg2rad(sideslip_deg)
62+
local_va = wind_speed * [
63+
cos(local_α)*cos(local_β),
64+
sin(local_β),
65+
sin(local_α)*cos(local_β)
66+
]
67+
set_va!(body_aero, local_va)
68+
69+
solve!(solver, body_aero)
70+
results_i = VortexStepMethod.solve(solver, body_aero; log=true)
71+
72+
push!(f_xs, results_i["Fx"])
73+
push!(f_ys, results_i["Fy"])
74+
push!(f_zs, results_i["Fz"])
75+
push!(m_xs, results_i["Mx"])
76+
push!(m_ys, results_i["My"])
77+
push!(m_zs, results_i["Mz"])
78+
79+
end
80+
81+
82+
# --- Plot results: true multi-row plotx style as in simple_lin_model.jl ---
83+
p = plotx(
84+
alphas,
85+
f_xs,
86+
f_ys,
87+
f_zs,
88+
m_xs,
89+
m_ys,
90+
m_zs;
91+
ylabels=["Fx [N]", "Fy [N]", "Fz [N]", "Mx [Nm]", "My [Nm]", "Mz [Nm]"],
92+
xlabel="alpha [deg]",
93+
fig="Aerodynamic Forces and Moments"
94+
)
95+
display(p)
96+
97+
98+
# Using plotting modules, to create more comprehensive plots
99+
PLOT = true
100+
USE_TEX = false
101+
102+
103+
# Plotting geometry
104+
PLOT && plot_geometry(
105+
body_aero,
106+
"";
107+
data_type=".svg",
108+
save_path="",
109+
is_save=false,
110+
is_show=true,
111+
view_elevation=15,
112+
view_azimuth=-120,
113+
use_tex=USE_TEX
114+
)
115+
116+
# Plotting polars
117+
PLOT && plot_polars(
118+
[solver],
119+
[body_aero],
120+
["VSM from Ram Air Kite OBJ and DAT file"],
121+
angle_range=range(0, 20, length=20),
122+
angle_type="angle_of_attack",
123+
angle_of_attack=5,
124+
side_slip=0,
125+
v_a=10,
126+
title="ram_kite_panels_$(ram_wing.n_panels)_distribution_$(ram_wing.spanwise_distribution)",
127+
data_type=".pdf",
128+
is_save=false,
129+
is_show=true,
130+
use_tex=USE_TEX
131+
)
132+
133+
# Plotting spanwise distributions
134+
body_y_coordinates = [panel.aero_center[2] for panel in body_aero.panels]
135+
136+
PLOT && plot_distribution(
137+
[body_y_coordinates],
138+
[results],
139+
["VSM"];
140+
title="CAD_spanwise_distributions_alpha_$(round(angle_of_attack_deg, digits=1))_delta_$(round(sideslip_deg, digits=1))_yaw_$(round(yaw_rate, digits=1))_v_a_$(round(wind_speed, digits=1))",
141+
data_type=".pdf",
142+
is_save=false,
143+
is_show=true,
144+
use_tex=USE_TEX
145+
)
146+
147+
148+
nothing

0 commit comments

Comments
 (0)