Skip to content

Commit a035f39

Browse files
1-Bort-1claude
andcommitted
Merge origin/main: make_dual_shadow builds every wing and keeps the reference point
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 parents 8a2b61d + 086ee32 commit a035f39

7 files changed

Lines changed: 213 additions & 167 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
doi:10.1088/1742-6596/2767/2/022068): each section gets a drag increment and a force
1313
along its span from the flow across it, in `solve!`, `solve` and `linearize`. Opt-in
1414
via `is_with_viscous_drag_correction` (default `false`) on the solver settings.
15+
- `linearize` takes a `BodyAerodynamics` with more than one wing; `theta_idxs` and
16+
`delta_idxs` then run over the unrefined sections of all wings in order.
1517

1618
### Changed
1719

@@ -26,6 +28,9 @@
2628
body z axis; it was read from the settings file and ignored.
2729
- The `VSMSolution` docstring gives `lift_dist`, `drag_dist` and `panel_moment_dist` in
2830
the per-unit-span units they hold, [N/m] and [Nm/m], instead of [N] and [Nm].
31+
- Inside its vortex core, `velocity_3D_trailing_vortex!` induces an azimuthal velocity
32+
instead of a radial one. Only points within the millimetre-scale Oseen core of a
33+
panel's chordwise trailing segment were affected.
2934
- With `artificial_damping` on, an iteration whose circulation is already smooth no longer
3035
re-applies the previous iteration's damping correction.
3136

docs/src/private_functions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ panel_loads
6363
```@docs
6464
velocity_3D_bound_vortex!
6565
velocity_3D_trailing_vortex!
66+
velocity_3D_vortex_segment!
6667
velocity_3D_trailing_vortex_semiinfinite!
6768
calculate_velocity_induced_bound_2D!
6869
calculate_velocity_induced_single_ring_semiinfinite!
@@ -83,6 +84,7 @@ calculate_filaments_for_plotting
8384
### Mesh refinement and billowing
8485
```@docs
8586
unrefined_deform!
87+
unrefined_section_range
8688
deform!
8789
compute_refined_panel_mapping!
8890
compute_refined_section_interpolation!

src/body_aerodynamics.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,37 @@ function calculate_stall_angle_list!(stall_angles::AbstractVector,
256256
return nothing
257257
end
258258

259+
"""
260+
unrefined_section_range(body_aero::BodyAerodynamics, wing_idx)
261+
262+
Indices of the unrefined sections of wing `wing_idx` in a distribution that runs over the
263+
unrefined sections of all wings in order, such as `moment_unrefined_dist`.
264+
"""
265+
function unrefined_section_range(body_aero::BodyAerodynamics, wing_idx)
266+
offset = 0
267+
for i in 1:wing_idx-1
268+
offset += body_aero.wings[i].n_unrefined_sections
269+
end
270+
return offset .+ (1:body_aero.wings[wing_idx].n_unrefined_sections)
271+
end
272+
273+
"""
274+
unrefined_deform!(body_aero::BodyAerodynamics, theta_angles, delta_angles)
275+
276+
Deform each wing of `body_aero` by its entries of `theta_angles` and `delta_angles` [rad],
277+
which run over the unrefined sections of all wings in order; `nothing` leaves that angle
278+
unchanged. Call [`reinit!`](@ref) afterwards to update the panels.
279+
"""
280+
function unrefined_deform!(body_aero::BodyAerodynamics, theta_angles, delta_angles)
281+
for (wing_idx, wing) in enumerate(body_aero.wings)
282+
section_range = unrefined_section_range(body_aero, wing_idx)
283+
unrefined_deform!(wing,
284+
isnothing(theta_angles) ? nothing : view(theta_angles, section_range),
285+
isnothing(delta_angles) ? nothing : view(delta_angles, section_range))
286+
end
287+
return nothing
288+
end
289+
259290
"""
260291
reinit!(body_aero::BodyAerodynamics; init_aero, va, omega, refine_mesh, recompute_mapping, sort_sections)
261292

src/filament.jl

Lines changed: 48 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ function reinit!(filament::BoundFilament{T}, x1, x2, vec=zeros(MVector{3, T})) w
3939
end
4040

4141
"""
42-
velocity_3D_bound_vortex(vel, filament::BoundFilament, XVP,
43-
gamma, core_radius_fraction, work_vectors)
42+
velocity_3D_bound_vortex!(vel, filament::BoundFilament, XVP,
43+
gamma, core_radius_fraction, work_vectors)
4444
45-
Calculate induced velocity by a bound vortex filament at a point in space.
45+
Calculate induced velocity by a bound vortex filament at a point in space, with a
46+
core radius of `core_radius_fraction` times the filament length.
4647
"""
4748
function velocity_3D_bound_vortex!(
4849
vel,
@@ -52,87 +53,24 @@ function velocity_3D_bound_vortex!(
5253
core_radius_fraction,
5354
work_vectors
5455
)
55-
r1, r2, r1Xr2, r1Xr0, r2Xr0, r1r2norm, r1_proj, r2_proj,
56-
r1_projXr2_proj, vel_ind_proj = work_vectors
57-
r0 = filament.r0
58-
nr0 = filament.length
59-
r1 .= XVP .- filament.x1
60-
r2 .= XVP .- filament.x2
61-
62-
epsilon = core_radius_fraction * nr0
63-
64-
cross3!(r1Xr0, r1, r0)
65-
66-
# Check point location relative to filament
67-
nr1Xr0 = norm3(r1Xr0)
68-
if nr1Xr0 / nr0 > epsilon
69-
cross3!(r1Xr2, r1, r2)
70-
nr1 = norm3(r1)
71-
nr2 = norm3(r2)
72-
@inbounds for k in 1:3
73-
r1r2norm[k] = r1[k]/nr1 - r2[k]/nr2
74-
end
75-
nr1Xr2 = norm3(r1Xr2)
76-
coeff = (gamma / (4π)) / (nr1Xr2^2) * dot3(r0, r1r2norm)
77-
@inbounds for k in 1:3
78-
vel[k] = coeff * r1Xr2[k]
79-
end
80-
elseif nr1Xr0 / nr0 < 1e-12 * epsilon
81-
vel .= 0.0
82-
else
83-
@debug "inside core radius"
84-
@debug "distance from control point to filament: $(nr1Xr0 / nr0)"
85-
86-
nr0sq = nr0 * nr0
87-
d_r1_r0 = dot3(r1, r0)
88-
d_r2_r0 = dot3(r2, r0)
89-
r_rad = r1Xr0
90-
@inbounds for k in 1:3
91-
r_rad[k] = r1[k] - d_r1_r0 * r0[k] / nr0sq
92-
end
93-
nr_rad = norm3(r_rad)
94-
@inbounds for k in 1:3
95-
r1_proj[k] = d_r1_r0 * r0[k] / nr0sq +
96-
epsilon * r_rad[k] / nr_rad
97-
r2_proj[k] = d_r2_r0 * r0[k] / nr0sq +
98-
epsilon * r_rad[k] / nr_rad
99-
end
100-
cross3!(r1_projXr2_proj, r1_proj, r2_proj)
101-
102-
nr1pXr2p = norm3(r1_projXr2_proj)
103-
nr1_proj = norm3(r1_proj)
104-
nr2_proj = norm3(r2_proj)
105-
d_sum = 0.0
106-
@inbounds for k in 1:3
107-
d_sum += r0[k] * (r1_proj[k]/nr1_proj -
108-
r2_proj[k]/nr2_proj)
109-
end
110-
coeff = (gamma / (4π)) / (nr1pXr2p^2) * d_sum
111-
@inbounds for k in 1:3
112-
vel_ind_proj[k] = coeff * r1_projXr2_proj[k]
113-
end
114-
115-
scale = nr1Xr0 / (nr0 * epsilon)
116-
@inbounds for k in 1:3
117-
vel[k] = scale * vel_ind_proj[k]
118-
end
119-
end
120-
nothing
56+
epsilon = core_radius_fraction * filament.length
57+
velocity_3D_vortex_segment!(vel, filament, XVP, gamma, epsilon, work_vectors)
12158
end
12259

12360
"""
124-
velocity_3D_trailing_vortex(vel, filament::BoundFilament,
125-
XVP, gamma, va, work_vectors)
61+
velocity_3D_trailing_vortex!(vel, filament::BoundFilament,
62+
XVP, gamma, va, work_vectors)
12663
127-
Calculate induced velocity by a trailing vortex filament.
64+
Calculate induced velocity by a trailing vortex filament, with a Lamb–Oseen core
65+
radius grown over the axial distance of `XVP` from the filament start.
12866
12967
# Arguments
13068
- `XVP`: Control point coordinates
13169
- `gamma`: Vortex strength
13270
- `va`: Inflow velocity magnitude
13371
- work_vectors: preallocated array of intermediate variables
13472
135-
Reference: Rick Damiani et al. "A vortex step method for nonlinear airfoil polar data
73+
Reference: Rick Damiani et al. "A vortex step method for nonlinear airfoil polar data
13674
as implemented in KiteAeroDyn".
13775
"""
13876
@inline function velocity_3D_trailing_vortex!(
@@ -143,71 +81,78 @@ as implemented in KiteAeroDyn".
14381
va,
14482
work_vectors
14583
)
146-
r1 = work_vectors[2]
147-
r2 = work_vectors[3]
148-
r_perp = work_vectors[4]
149-
r1Xr2 = work_vectors[5]
150-
r1Xr0 = work_vectors[6]
151-
r2Xr0 = work_vectors[7]
152-
normr1r2 = work_vectors[8]
84+
r1 = work_vectors[1]
85+
r1 .= XVP .- filament.x1
86+
axial_distance = abs(dot3(r1, filament.r0)) / filament.length
87+
epsilon = sqrt(4 * ALPHA0 * NU * axial_distance / va)
88+
velocity_3D_vortex_segment!(vel, filament, XVP, gamma, epsilon, work_vectors)
89+
end
15390

91+
"""
92+
velocity_3D_vortex_segment!(vel, filament::BoundFilament, XVP,
93+
gamma, epsilon, work_vectors)
94+
95+
Calculate the Biot–Savart velocity induced by a straight vortex segment at `XVP`.
96+
Inside the core radius `epsilon` the velocity is evaluated on the core boundary and
97+
scaled linearly with the distance to the axis.
98+
"""
99+
@inline function velocity_3D_vortex_segment!(
100+
vel,
101+
filament::BoundFilament,
102+
XVP,
103+
gamma,
104+
epsilon,
105+
work_vectors
106+
)
107+
r1, r2, r1Xr2, r1Xr0, r1r2norm, r1_proj, r2_proj = work_vectors
154108
r0 = filament.r0
155109
nr0 = filament.length
156110
r1 .= XVP .- filament.x1
157111
r2 .= XVP .- filament.x2
158112

159-
nr0sq = nr0 * nr0
160-
d_r1_r0 = dot3(r1, r0)
161-
162-
# Cut-off radius. The perpendicular component has length |r1.r0|/|r0|, so the
163-
# vector itself is only needed inside the core.
164-
epsilon = sqrt(4 * ALPHA0 * NU * abs(d_r1_r0) / nr0 / va)
165-
166113
cross3!(r1Xr0, r1, r0)
167-
168-
# Check point location relative to filament
169114
nr1Xr0 = norm3(r1Xr0)
170115
if nr1Xr0 / nr0 > epsilon
171116
cross3!(r1Xr2, r1, r2)
172117
nr1 = norm3(r1)
173118
nr2 = norm3(r2)
174119
@inbounds for k in 1:3
175-
normr1r2[k] = r1[k]/nr1 - r2[k]/nr2
120+
r1r2norm[k] = r1[k]/nr1 - r2[k]/nr2
176121
end
177122
nr1Xr2 = norm3(r1Xr2)
178-
coeff = (gamma / (4π)) / (nr1Xr2^2) * dot3(r0, normr1r2)
123+
coeff = (gamma / (4π)) / (nr1Xr2^2) * dot3(r0, r1r2norm)
179124
@inbounds for k in 1:3
180125
vel[k] = coeff * r1Xr2[k]
181126
end
182127
elseif nr1Xr0 / nr0 < 1e-12 * epsilon
183128
vel .= 0.0
184129
else
185-
# Project onto core radius — reuse r_perp, normr1r2
186-
r1_proj = r_perp
187-
r2_proj = normr1r2
188-
cross3!(r2Xr0, r2, r0)
189-
nr2Xr0 = norm3(r2Xr0)
130+
nr0sq = nr0 * nr0
131+
d_r1_r0 = dot3(r1, r0)
190132
d_r2_r0 = dot3(r2, r0)
133+
r_rad = r1Xr0
134+
@inbounds for k in 1:3
135+
r_rad[k] = r1[k] - d_r1_r0 * r0[k] / nr0sq
136+
end
137+
nr_rad = norm3(r_rad)
191138
@inbounds for k in 1:3
192139
r1_proj[k] = d_r1_r0 * r0[k] / nr0sq +
193-
epsilon * r1Xr0[k] / nr1Xr0
140+
epsilon * r_rad[k] / nr_rad
194141
r2_proj[k] = d_r2_r0 * r0[k] / nr0sq +
195-
epsilon * r2Xr0[k] / nr2Xr0
142+
epsilon * r_rad[k] / nr_rad
196143
end
197-
198144
cross3!(r1Xr2, r1_proj, r2_proj)
199-
nr1Xr2_val = norm3(r1Xr2)
200145
nr1_proj = norm3(r1_proj)
201146
nr2_proj = norm3(r2_proj)
202147
d_sum = 0.0
203148
@inbounds for k in 1:3
204149
d_sum += r0[k] * (r1_proj[k]/nr1_proj -
205150
r2_proj[k]/nr2_proj)
206151
end
207-
coeff = (gamma / (4π)) / (nr1Xr2_val^2) * d_sum
208152
scale = nr1Xr0 / (nr0 * epsilon)
153+
coeff = scale * (gamma / (4π)) / (norm3(r1Xr2)^2) * d_sum
209154
@inbounds for k in 1:3
210-
vel[k] = scale * coeff * r1Xr2[k]
155+
vel[k] = coeff * r1Xr2[k]
211156
end
212157
end
213158
nothing
@@ -263,8 +208,7 @@ function velocity_3D_trailing_vortex_semiinfinite!(
263208
GAMMA = -GAMMA * filament.filament_direction
264209
r1 .= XVP .- filament.x1
265210

266-
# Core radius. `r_perp` is `(r1.Vf) Vf`, so its length is `|r1.Vf| |Vf|` and
267-
# the vector itself is only needed inside the core.
211+
# Core radius, grown with the axial distance of `XVP` along `Vf`.
268212
d_r1_Vf = dot3(r1, Vf)
269213
nVf = norm3(Vf)
270214
epsilon = sqrt(4 * ALPHA0 * NU * abs(d_r1_Vf) * nVf / va)

0 commit comments

Comments
 (0)