Skip to content

Commit a44ed7d

Browse files
1-Bart-1claude
andcommitted
Split calc_forces! out of solve! and make it zero-alloc
Extract the force/moment assembly (everything after the circulation solve) from solve! into a standalone exported calc_forces!(solver, body_aero). solve! is now solve_base! + calc_forces!; behavior is unchanged. This lets a frozen circulation be mapped to forces without re-running the nonlinear gamma solve. Make calc_forces! allocation-free for the per-step hot path: - preallocate panel_area_dist buffer (was zeros(T, n) each call) - preallocate unrefined_count_dist buffer (was zeros(Int, n) each call) - rewrite _compute_reference_velocity_from_distribution to drop its ones()/T.() copies Add a function-barrier zero-alloc test for calc_forces!. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1db9add commit a44ed7d

4 files changed

Lines changed: 61 additions & 21 deletions

File tree

src/VortexStepMethod.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ using Xfoil
2929
export SolverSettings, VSMSettings, WingSettings
3030
export ObjWing, Section, Wing, refine!, reinit!
3131
export BodyAerodynamics
32-
export Solver, VSMSolution, linearize, solve, solve!, solve_base!
32+
export Solver, VSMSolution, linearize, solve, solve!, solve_base!, calc_forces!
3333
export calculate_results
3434
export add_section!, set_va!
3535
export calculate_projected_area, calculate_span

src/body_aerodynamics.jl

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -315,28 +315,29 @@ end
315315
)
316316
size(va_input) == (n_panels, 3) ||
317317
throw(ArgumentError("'va' must be shape (3,) or ($(n_panels), 3); got $(size(va_input))"))
318-
319-
T = promote_type(eltype(va_input),
320-
isnothing(panel_areas) ? Float64 : eltype(panel_areas))
321-
areas = if isnothing(panel_areas)
322-
ones(T, n_panels)
323-
else
318+
if !isnothing(panel_areas)
324319
length(panel_areas) == n_panels ||
325320
throw(ArgumentError("panel_areas must be shape ($(n_panels),), got length $(length(panel_areas))"))
326-
T.(panel_areas)
327321
end
328322

329-
total_area = sum(areas)
330-
total_area > 0.0 || throw(ArgumentError("Total panel area must be positive."))
331-
323+
T = promote_type(eltype(va_input),
324+
isnothing(panel_areas) ? Float64 : eltype(panel_areas))
325+
total_area = zero(T)
332326
weighted_speed_sq = zero(T)
333327
direction = zeros(MVector{3, T})
334328
@inbounds for i in 1:n_panels
335-
@views va_i = va_input[i, :]
336-
speed_i = norm(va_i)
337-
weighted_speed_sq += areas[i] * speed_i^2
338-
direction .+= areas[i] .* va_i
329+
area_i = isnothing(panel_areas) ? one(T) : T(panel_areas[i])
330+
va1 = va_input[i, 1]
331+
va2 = va_input[i, 2]
332+
va3 = va_input[i, 3]
333+
speed_i = sqrt(va1^2 + va2^2 + va3^2)
334+
total_area += area_i
335+
weighted_speed_sq += area_i * speed_i^2
336+
direction[1] += area_i * va1
337+
direction[2] += area_i * va2
338+
direction[3] += area_i * va3
339339
end
340+
total_area > 0.0 || throw(ArgumentError("Total panel area must be positive."))
340341

341342
reference_speed = sqrt(weighted_speed_sq / total_area)
342343
direction_norm = norm(direction)

src/solver.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Struct for storing the solution of the [solve!](@ref) function. Must contain all
4343
_chord_dist::Vector{T} = zeros(T, P)
4444
### end of private vectors
4545
width_dist::Vector{T} = zeros(T, P)
46+
panel_area_dist::Vector{T} = zeros(T, P)
4647
alpha_dist::Vector{T} = zeros(T, P)
4748
alpha_geometric_dist::Vector{T} = zeros(T, P)
4849
cl_dist::Vector{T} = zeros(T, P)
@@ -74,6 +75,7 @@ Struct for storing the solution of the [solve!](@ref) function. Must contain all
7475
va_unrefined_dist::Vector{MVector{3, T}} = [zeros(MVector{3, T}) for _ in 1:U]
7576
chord_unrefined_dist::MVector{U, T} = zeros(MVector{U, T})
7677
width_unrefined_dist::MVector{U, T} = zeros(MVector{U, T})
78+
unrefined_count_dist::Vector{Int} = zeros(Int, U)
7779
solver_status::SolverStatus = FAILURE
7880
end
7981

@@ -237,6 +239,20 @@ function solve!(solver::Solver{P, U, T}, body_aero::BodyAerodynamics, gamma_dist
237239
solver.sol.gamma_distribution = gamma_new
238240
end
239241

242+
return calc_forces!(solver, body_aero; reference_point, moment_frac)
243+
end
244+
245+
"""
246+
calc_forces!(solver::Solver, body_aero::BodyAerodynamics;
247+
reference_point=solver.reference_point, moment_frac=0.1)
248+
249+
Assemble aerodynamic forces and moments from the circulation already converged
250+
by [`solve_base!`](@ref) and stored in `solver`. Split out of [`solve!`](@ref).
251+
"""
252+
function calc_forces!(solver::Solver{P, U, T}, body_aero::BodyAerodynamics;
253+
reference_point=solver.reference_point, moment_frac=0.1) where {P, U, T}
254+
gamma_new = solver.lr.gamma_new
255+
240256
# Initialize arrays
241257
cl_dist = solver.sol.cl_dist
242258
cd_dist = solver.sol.cd_dist
@@ -322,7 +338,7 @@ function solve!(solver::Solver{P, U, T}, body_aero::BodyAerodynamics, gamma_dist
322338

323339
# Initialize result arrays
324340
area_all_panels = zero(T)
325-
panel_areas = zeros(T, length(panels))
341+
panel_areas = solver.sol.panel_area_dist
326342

327343
# Get wing properties
328344
spanwise_direction = body_aero.wings[1].spanwise_direction
@@ -428,6 +444,7 @@ function solve!(solver::Solver{P, U, T}, body_aero::BodyAerodynamics, gamma_dist
428444
va_unrefined_dist = solver.sol.va_unrefined_dist
429445
chord_unrefined_dist = solver.sol.chord_unrefined_dist
430446
width_unrefined_dist = solver.sol.width_unrefined_dist
447+
unrefined_count_dist = solver.sol.unrefined_count_dist
431448

432449
# Zero all unrefined arrays
433450
moment_unrefined_dist .= 0.0
@@ -444,13 +461,12 @@ function solve!(solver::Solver{P, U, T}, body_aero::BodyAerodynamics, gamma_dist
444461
end
445462
chord_unrefined_dist .= 0.0
446463
width_unrefined_dist .= 0.0
464+
fill!(unrefined_count_dist, 0)
447465

448466
panel_idx = 1
449467
unrefined_idx = 1
450468
for wing in body_aero.wings
451469
if wing.n_unrefined_sections > 0
452-
# Accumulate values from refined panels to unrefined sections
453-
unrefined_section_counts = zeros(Int, wing.n_unrefined_sections)
454470
for local_panel_idx in 1:wing.n_panels
455471
panel = body_aero.panels[panel_idx]
456472
original_section_idx = wing.refined_panel_mapping[local_panel_idx]
@@ -472,16 +488,16 @@ function solve!(solver::Solver{P, U, T}, body_aero::BodyAerodynamics, gamma_dist
472488
chord_unrefined_dist[target_unrefined_idx] += panel.chord
473489
width_unrefined_dist[target_unrefined_idx] += panel.width
474490

475-
unrefined_section_counts[original_section_idx] += 1
491+
unrefined_count_dist[target_unrefined_idx] += 1
476492
panel_idx += 1
477493
end
478494

479495
# Average coefficients and geometry. width and
480496
# moment_coeff_unrefined_dist stay summed (extensive).
481497
for i in 1:wing.n_unrefined_sections
482498
target_unrefined_idx = unrefined_idx + i - 1
483-
if unrefined_section_counts[i] > 0
484-
count = unrefined_section_counts[i]
499+
if unrefined_count_dist[target_unrefined_idx] > 0
500+
count = unrefined_count_dist[target_unrefined_idx]
485501
moment_unrefined_dist[target_unrefined_idx] /= count
486502
cl_unrefined_dist[target_unrefined_idx] /= count
487503
cd_unrefined_dist[target_unrefined_idx] /= count

test/solver/test_solver.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,26 @@ end
6868
rm(settings_file; force=true)
6969
end
7070
end
71+
72+
calc_forces_allocs(solver, body_aero) =
73+
(calc_forces!(solver, body_aero); @allocated calc_forces!(solver, body_aero))
74+
75+
@testset "calc_forces! is zero-alloc" begin
76+
settings_file = create_temp_wing_settings(
77+
"solver", "solver_test_wing.yaml";
78+
alpha=5.0, beta=0.0, wind_speed=10.0,
79+
)
80+
try
81+
settings = VSMSettings(settings_file)
82+
wing = Wing(settings)
83+
refine!(wing)
84+
body_aero = BodyAerodynamics([wing])
85+
solver = Solver(body_aero, settings)
86+
set_va!(body_aero, [10.0, 0.0, 0.0])
87+
solve!(solver, body_aero)
88+
89+
@test calc_forces_allocs(solver, body_aero) == 0
90+
finally
91+
rm(settings_file; force=true)
92+
end
93+
end

0 commit comments

Comments
 (0)