Skip to content

Commit af11c40

Browse files
committed
#155 #185 added StructMapping for yaml parsing
1 parent 8942409 commit af11c40

File tree

4 files changed

+136
-64
lines changed

4 files changed

+136
-64
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
2323
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
2424
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2525
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
26+
StructMapping = "7c3b921d-20d6-4eba-ad57-605525e86ab2"
2627
Timers = "21f18d07-b854-4dab-86f0-c15a3821819a"
2728
Xfoil = "19641d66-a62d-11e8-2441-8f57a969a9c4"
2829
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
@@ -59,6 +60,7 @@ SciMLBase = "2.77.0"
5960
Serialization = "1"
6061
StaticArrays = "1"
6162
Statistics = "1"
63+
StructMapping = "0.2.3"
6264
Test = "1"
6365
Timers = "0.1"
6466
Xfoil = "0.5"
@@ -75,4 +77,4 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
7577
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
7678

7779
[targets]
78-
test = ["Test", "DataFrames", "CSV", "Documenter", "BenchmarkTools", "ControlPlots", "Aqua"]
80+
test = ["Test", "DataFrames", "CSV", "Documenter", "BenchmarkTools", "ControlPlots", "Aqua"]

src/VortexStepMethod.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ using LaTeXStrings
1212
using NonlinearSolve
1313
import NonlinearSolve: solve!, solve
1414
using Interpolations
15-
using Interpolations: Extrapolation
15+
import Interpolations: Extrapolation
1616
using Parameters
1717
using Serialization
1818
using Timers
@@ -22,6 +22,7 @@ using Pkg
2222
using DifferentiationInterface
2323
import SciMLBase: successful_retcode
2424
import YAML
25+
using StructMapping
2526
using Xfoil
2627

2728
# Export public interface
@@ -40,13 +41,12 @@ export InitialGammaDistribution, ELLIPTIC, ZEROS
4041
export SolverStatus, FEASIBLE, INFEASIBLE, FAILURE
4142
export SolverType, LOOP, NONLIN
4243

43-
export plot_geometry, plot_distribution, plot_circulation_distribution, plot_geometry, plot_polars, save_plot, show_plot, plot_polar_data
44+
export plot_geometry, plot_distribution, plot_circulation_distribution, plot_polars, save_plot, show_plot, plot_polar_data
4445

4546
# the following functions are defined in ext/VortexStepMethodExt.jl
4647
function plot_geometry end
4748
function plot_distribution end
4849
function plot_circulation_distribution end
49-
function plot_geometry end
5050
function plot_polars end
5151
function save_plot end
5252
function show_plot end

src/settings.jl

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,56 +41,63 @@ end
4141
end
4242

4343
function vs(filename)
44-
res = VSMSettings()
44+
# Uwe's suggested 3-line approach using StructMapping.jl (adapted)
4545
data = YAML.load_file(joinpath("data", filename))
46-
n_panels = 0
47-
n_groups = 0
4846

49-
# Load condition settings if present
47+
# Use StructMapping for basic structure conversion
48+
# But handle special fields manually due to enum conversion needs
49+
vsm_settings = VSMSettings()
50+
51+
# Convert condition settings using StructMapping
5052
if haskey(data, "condition")
51-
condition = data["condition"]
52-
res.condition.wind_speed = condition["wind_speed"]
53-
res.condition.alpha = condition["alpha"]
54-
res.condition.beta = condition["beta"]
55-
res.condition.yaw_rate = condition["yaw_rate"]
53+
vsm_settings.condition = convertdict(ConditionSettings, data["condition"])
5654
end
5755

58-
# add and update wing settings
59-
for (i, wing) in pairs(data["wings"])
60-
push!(res.wings, WingSettings())
61-
res.wings[i].name = wing["name"]
62-
if haskey(wing, "geometry_file")
63-
res.wings[i].geometry_file = wing["geometry_file"]
56+
# Convert wing settings manually due to enum conversions
57+
n_panels = 0
58+
n_groups = 0
59+
60+
if haskey(data, "wings")
61+
for wing_data in data["wings"]
62+
wing = WingSettings()
63+
wing.name = wing_data["name"]
64+
if haskey(wing_data, "geometry_file")
65+
wing.geometry_file = wing_data["geometry_file"]
66+
end
67+
wing.n_panels = wing_data["n_panels"]
68+
wing.n_groups = wing_data["n_groups"]
69+
wing.spanwise_panel_distribution = eval(Symbol(wing_data["spanwise_panel_distribution"]))
70+
wing.spanwise_direction = MVec3(wing_data["spanwise_direction"])
71+
wing.remove_nan = wing_data["remove_nan"]
72+
73+
push!(vsm_settings.wings, wing)
74+
n_panels += wing.n_panels
75+
n_groups += wing.n_groups
6476
end
65-
res.wings[i].n_panels = wing["n_panels"]
66-
n_panels += res.wings[i].n_panels
67-
res.wings[i].n_groups = wing["n_groups"]
68-
n_groups += res.wings[i].n_groups
69-
res.wings[i].spanwise_panel_distribution = eval(Symbol(wing["spanwise_panel_distribution"]))
70-
res.wings[i].spanwise_direction = MVec3(wing["spanwise_direction"])
71-
res.wings[i].remove_nan = wing["remove_nan"]
7277
end
73-
# update solver settings
74-
solver = data["solver_settings"]
75-
res.solver_settings.n_panels = n_panels
76-
res.solver_settings.n_groups = n_groups
77-
res.solver_settings.aerodynamic_model_type = eval(Symbol(solver["aerodynamic_model_type"]))
78-
if haskey(solver, "solver_type")
79-
res.solver_settings.solver_type = solver["solver_type"]
78+
79+
# Convert solver settings using StructMapping base, then override special fields
80+
if haskey(data, "solver_settings")
81+
solver_data = data["solver_settings"]
82+
83+
# Create a copy of solver_data with string fields for enums removed
84+
solver_data_clean = copy(solver_data)
85+
delete!(solver_data_clean, "aerodynamic_model_type")
86+
delete!(solver_data_clean, "type_initial_gamma_distribution")
87+
88+
# Use StructMapping for the basic fields
89+
vsm_settings.solver_settings = convertdict(SolverSettings, solver_data_clean)
90+
91+
# Handle enum conversions manually
92+
vsm_settings.solver_settings.aerodynamic_model_type = eval(Symbol(solver_data["aerodynamic_model_type"]))
93+
vsm_settings.solver_settings.type_initial_gamma_distribution = eval(Symbol(solver_data["type_initial_gamma_distribution"]))
94+
95+
# Override with calculated totals
96+
vsm_settings.solver_settings.n_panels = n_panels
97+
vsm_settings.solver_settings.n_groups = n_groups
8098
end
81-
res.solver_settings.density = solver["density"]
82-
res.solver_settings.max_iterations = solver["max_iterations"]
83-
res.solver_settings.rtol = solver["rtol"]
84-
res.solver_settings.tol_reference_error = solver["tol_reference_error"]
85-
res.solver_settings.relaxation_factor = solver["relaxation_factor"]
86-
res.solver_settings.artificial_damping = solver["artificial_damping"]
87-
res.solver_settings.k2 = solver["k2"]
88-
res.solver_settings.k4 = solver["k4"]
89-
res.solver_settings.type_initial_gamma_distribution = eval(Symbol(solver["type_initial_gamma_distribution"]))
90-
res.solver_settings.core_radius_fraction = solver["core_radius_fraction"]
91-
res.solver_settings.mu = solver["mu"]
92-
res.solver_settings.calc_only_f_and_gamma = solver["calc_only_f_and_gamma"]
93-
res
99+
100+
return vsm_settings
94101
end
95102

96103
function Base.show(io::IO, vs::VSMSettings)

src/yaml_geometry.jl

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,45 @@ Expected format: header row and columns alpha, cl, cd, cm (case-insensitive, ord
1212
1313
Returns (aero_data, POLAR_VECTORS) or (nothing, INVISCID) if missing/invalid.
1414
"""
15+
16+
# Structs for YAML parsing using StructMapping.jl
17+
@kwdef struct WingAirfoilInfo
18+
csv_file_path::String = ""
19+
end
20+
21+
@kwdef struct WingAirfoilData
22+
airfoil_id::Int64
23+
type::String
24+
info_dict::WingAirfoilInfo
25+
end
26+
27+
@kwdef struct WingAirfoils
28+
alpha_range::Vector{Float64}
29+
reynolds::Float64
30+
headers::Vector{String}
31+
data::Vector{WingAirfoilData}
32+
end
33+
34+
@kwdef struct WingSectionData
35+
airfoil_id::Int64
36+
LE_x::Float64
37+
LE_y::Float64
38+
LE_z::Float64
39+
TE_x::Float64
40+
TE_y::Float64
41+
TE_z::Float64
42+
end
43+
44+
@kwdef struct WingSections
45+
headers::Vector{String}
46+
data::Vector{WingSectionData}
47+
end
48+
49+
@kwdef struct WingGeometry
50+
wing_sections::WingSections
51+
wing_airfoils::WingAirfoils
52+
end
53+
1554
function load_polar_data(csv_file_path::String)
1655
if isempty(csv_file_path) || !isfile(csv_file_path)
1756
@warn "Polar file not found or empty path: $csv_file_path. Using INVISCID model instead."
@@ -126,17 +165,44 @@ function YamlWing(
126165

127166
prn && @info "Reading YAML wing configuration from $geometry_file"
128167

129-
# Read and parse YAML file
130-
yaml_data = YAML.load_file(geometry_file)
168+
# Load YAML file following Uwe's suggestion
169+
data = YAML.load_file(geometry_file)
131170

132-
# Parse airfoils and create CSV file mapping
133-
airfoil_csv_map = Dict{Int64, String}()
171+
# Convert YAML data to our struct format
172+
# Convert wing sections
173+
wing_sections_data = data["wing_sections"]
174+
sections = WingSectionData[]
175+
for row in wing_sections_data["data"]
176+
section_dict = Dict(zip(wing_sections_data["headers"], row))
177+
push!(sections, WingSectionData(
178+
airfoil_id = section_dict["airfoil_id"],
179+
LE_x = section_dict["LE_x"],
180+
LE_y = section_dict["LE_y"],
181+
LE_z = section_dict["LE_z"],
182+
TE_x = section_dict["TE_x"],
183+
TE_y = section_dict["TE_y"],
184+
TE_z = section_dict["TE_z"]
185+
))
186+
end
134187

135-
for row in yaml_data["wing_airfoils"]["data"]
136-
airfoil_dict = Dict(zip(yaml_data["wing_airfoils"]["headers"], row))
137-
airfoil_id, airfoil_type, info_dict = airfoil_dict["airfoil_id"], airfoil_dict["type"], airfoil_dict["info_dict"]
138-
139-
haskey(info_dict, "csv_file_path") && (airfoil_csv_map[airfoil_id] = info_dict["csv_file_path"])
188+
# Convert wing airfoils
189+
wing_airfoils_data = data["wing_airfoils"]
190+
airfoils = WingAirfoilData[]
191+
for row in wing_airfoils_data["data"]
192+
airfoil_dict = Dict(zip(wing_airfoils_data["headers"], row))
193+
push!(airfoils, WingAirfoilData(
194+
airfoil_id = airfoil_dict["airfoil_id"],
195+
type = airfoil_dict["type"],
196+
info_dict = WingAirfoilInfo(csv_file_path = get(airfoil_dict["info_dict"], "csv_file_path", ""))
197+
))
198+
end
199+
200+
# Create CSV file mapping from airfoils
201+
airfoil_csv_map = Dict{Int64, String}()
202+
for airfoil in airfoils
203+
if !isempty(airfoil.info_dict.csv_file_path)
204+
airfoil_csv_map[airfoil.airfoil_id] = airfoil.info_dict.csv_file_path
205+
end
140206
end
141207

142208
# Create Wing using the standard constructor
@@ -148,23 +214,20 @@ function YamlWing(
148214
)
149215

150216
# Parse sections and populate wing
151-
for row in yaml_data["wing_sections"]["data"]
152-
section_dict = Dict(zip(yaml_data["wing_sections"]["headers"], row))
153-
airfoil_id = section_dict["airfoil_id"]
154-
155-
# Get coordinates
156-
le_coord = [section_dict["LE_x"], section_dict["LE_y"], section_dict["LE_z"]]
157-
te_coord = [section_dict["TE_x"], section_dict["TE_y"], section_dict["TE_z"]]
217+
for section in sections
218+
# Get coordinates directly from struct fields
219+
le_coord = [section.LE_x, section.LE_y, section.LE_z]
220+
te_coord = [section.TE_x, section.TE_y, section.TE_z]
158221

159222
# Load polar data and create section
160-
csv_file_path = get(airfoil_csv_map, airfoil_id, "")
223+
csv_file_path = get(airfoil_csv_map, section.airfoil_id, "")
161224
if !isempty(csv_file_path) && !isabspath(csv_file_path)
162225
# Make relative paths relative to YAML file directory
163226
csv_file_path = joinpath(dirname(geometry_file), csv_file_path)
164227
end
165228
aero_data, aero_model = load_polar_data(csv_file_path)
166229

167-
prn && println("Section airfoil_id $airfoil_id: Using $aero_model model")
230+
prn && println("Section airfoil_id $(section.airfoil_id): Using $aero_model model")
168231

169232
add_section!(wing, le_coord, te_coord, aero_model, aero_data)
170233
end

0 commit comments

Comments
 (0)