Skip to content

Commit bd7d9c8

Browse files
committed
fix fgs for panel and seminfinite wake
1 parent 4f86624 commit bd7d9c8

15 files changed

+171
-2709
lines changed

src/FLOWPanel.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import GeometricTools: Meshes
3838
import ImplicitAD as IAD
3939
import ImplicitAD: ForwardDiff as FD, ReverseDiff as RD
4040
import FLOWMath as math
41+
using WriteVTK
4142

4243
# ------------ GLOBAL VARIABLES AND DATA STRUCTURES ----------------------------
4344
const module_path = splitdir(@__FILE__)[1] # Path to this module

src/FLOWPanel_abstractliftingbody.jl

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -232,58 +232,33 @@ function _G_phi_wake!(self::AbstractLiftingBody{<:Any,<:Any,TF}, kernel, G, CPs,
232232
Das, Dbs = self.Das, self.Dbs
233233
derivatives_switch = FastMultipole.DerivativesSwitch(true,false,false)
234234

235-
# for chunk in chunks # Distribute wake panel iteration among all CPU threads
236-
# Threads.@threads for chunk in chunks # Distribute wake panel iteration among all CPU threads
237-
for chunk in chunks # Distribute wake panel iteration among all CPU threads
238-
239-
# for (ei, (pi, nia, nib, pj, nja, njb)) in enumerate(eachcol(self.shedding))
240-
for i_source in chunk # Iterate over wake-shedding panels
241-
242-
pi, nia, nib, pj, nja, njb = view(self.shedding, :, i_source)
243-
244-
# Fetch nodes of upper wake panel
245-
nodes_idx = (self.cells[1, pi], self.cells[2, pi], self.cells[3, pi])
246-
247-
TE1 = nodes_idx[nia]
248-
TE2 = nodes_idx[nib]
249-
v1x = self.grid._nodes[1, TE1]
250-
v1y = self.grid._nodes[2, TE1]
251-
v1z = self.grid._nodes[3, TE1]
252-
v2x = self.grid._nodes[1, TE2]
253-
v2y = self.grid._nodes[2, TE2]
254-
v2z = self.grid._nodes[3, TE2]
255-
256-
# direction of trailing semi-infinite wake
257-
da1, da2, da3 = Das[1, i_source], Das[2, i_source], Das[3, i_source]
258-
db1, db2, db3 = Dbs[1, i_source], Dbs[2, i_source], Dbs[3, i_source]
259-
@assert isapprox(da1, db1) && isapprox(da2, db2) && isapprox(da3, db3) "Inconsistent wake directions in _G_phi_wake!"
260-
261-
for i_target in axes(CPs, 2)
262-
# get target
263-
tx, ty, tz = CPs[1, i_target], CPs[2, i_target], CPs[3, i_target]
264-
target = FastMultipole.StaticArrays.SVector{3,TF}(tx, ty, tz)
265-
266-
# compute influence
267-
phi, _ = induced_semiinfinite(target, kernel, v1x, v1y, v1z, v2x, v2y, v2z, da1, da2, da3, 1.0, derivatives_switch; kerneloffset)
268-
269-
# update G
270-
G[i_target, pi] += phi
271-
end
235+
for isurf in eachindex(self.shedding)
236+
# for chunk in chunks # Distribute wake panel iteration among all CPU threads
237+
# Threads.@threads for chunk in chunks # Distribute wake panel iteration among all CPU threads
238+
for chunk in chunks # Distribute wake panel iteration among all CPU threads
239+
240+
# for (ei, (pi, nia, nib, pj, nja, njb)) in enumerate(eachcol(self.shedding))
241+
for i_source in chunk # Iterate over wake-shedding panels
242+
243+
pi, nia, nib, pj, nja, njb = view(self.shedding[isurf], :, i_source)
272244

273-
# lower wake panel (if it exists)
274-
if pj != -1
275-
# Fetch nodes of lower wake panel
276-
nodes_idx = (self.cells[1, pj], self.cells[2, pj], self.cells[3, pj])
245+
# Fetch nodes of upper wake panel
246+
nodes_idx = (self.cells[1, pi], self.cells[2, pi], self.cells[3, pi])
277247

278-
TE1 = nodes_idx[nja]
279-
TE2 = nodes_idx[njb]
248+
TE1 = nodes_idx[nia]
249+
TE2 = nodes_idx[nib]
280250
v1x = self.grid._nodes[1, TE1]
281251
v1y = self.grid._nodes[2, TE1]
282252
v1z = self.grid._nodes[3, TE1]
283253
v2x = self.grid._nodes[1, TE2]
284254
v2y = self.grid._nodes[2, TE2]
285255
v2z = self.grid._nodes[3, TE2]
286256

257+
# direction of trailing semi-infinite wake
258+
da1, da2, da3 = Das[isurf][1, i_source], Das[isurf][2, i_source], Das[isurf][3, i_source]
259+
db1, db2, db3 = Dbs[isurf][1, i_source], Dbs[isurf][2, i_source], Dbs[isurf][3, i_source]
260+
@assert isapprox(da1, db1) && isapprox(da2, db2) && isapprox(da3, db3) "Inconsistent wake directions in _G_phi_wake!"
261+
287262
for i_target in axes(CPs, 2)
288263
# get target
289264
tx, ty, tz = CPs[1, i_target], CPs[2, i_target], CPs[3, i_target]
@@ -293,9 +268,36 @@ function _G_phi_wake!(self::AbstractLiftingBody{<:Any,<:Any,TF}, kernel, G, CPs,
293268
phi, _ = induced_semiinfinite(target, kernel, v1x, v1y, v1z, v2x, v2y, v2z, da1, da2, da3, 1.0, derivatives_switch; kerneloffset)
294269

295270
# update G
296-
G[i_target, pj] += phi
271+
G[i_target, pi] += phi
297272
end
298273

274+
# lower wake panel (if it exists)
275+
if pj != -1
276+
# Fetch nodes of lower wake panel
277+
nodes_idx = (self.cells[1, pj], self.cells[2, pj], self.cells[3, pj])
278+
279+
TE1 = nodes_idx[nja]
280+
TE2 = nodes_idx[njb]
281+
v1x = self.grid._nodes[1, TE1]
282+
v1y = self.grid._nodes[2, TE1]
283+
v1z = self.grid._nodes[3, TE1]
284+
v2x = self.grid._nodes[1, TE2]
285+
v2y = self.grid._nodes[2, TE2]
286+
v2z = self.grid._nodes[3, TE2]
287+
288+
for i_target in axes(CPs, 2)
289+
# get target
290+
tx, ty, tz = CPs[1, i_target], CPs[2, i_target], CPs[3, i_target]
291+
target = FastMultipole.StaticArrays.SVector{3,TF}(tx, ty, tz)
292+
293+
# compute influence
294+
phi, _ = induced_semiinfinite(target, kernel, v1x, v1y, v1z, v2x, v2y, v2z, da1, da2, da3, 1.0, derivatives_switch; kerneloffset)
295+
296+
# update G
297+
G[i_target, pj] += phi
298+
end
299+
300+
end
299301
end
300302
end
301303
end

src/FLOWPanel_liftingbody.jl

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -536,18 +536,21 @@ function solve2!(self::RigidWakeBody{Union{ConstantSource, ConstantDoublet}, 2,
536536
self.strength[:, 2] .= 0.0
537537

538538
# calculate potential due to source strengths
539-
_phi!(self, CPs, self.potential, backend; include_wake=false, optargs...) # NOTE: wake is included in the `direct!` function in this line
539+
self.potential .= 0.0
540+
_phi!(self, CPs, self.potential, backend; include_wake=false, optargs...)
540541

541542
# zero source strengths while we solver for the doublet strengths
542543
self.strength[:, 1] .= 0.0
543544

544545
# run fgs solver
545546
FastMultipole.solve!(self, solver.fgs;
546-
max_iterations=solver.max_iterations,
547+
max_iterations=solver.max_iterations,
548+
inner_iterations=solver.inner_iterations,
547549
tolerance=solver.tolerance,
548550
rlx=solver.rlx,
549551
derivatives_switches=FastMultipole.DerivativesSwitch(true, false, false, to_tuple(_unpack_fmm(self))),
550-
reverse_pass=false
552+
reverse_pass=solver.reverse_pass,
553+
verbose=solver.verbose,
551554
)
552555

553556
# update source strength
@@ -2063,7 +2066,6 @@ function FastMultipole.body_to_multipole!(system::RigidWakeBody{Union{ConstantSo
20632066
# which vertices connect to the wake
20642067
idx1 = Int(buffer[end-7, i_body])
20652068
if idx1 > 0
2066-
println("wake...")
20672069
# first vertex and wake
20682070
Dax, Day, Daz = buffer[end-6, i_body], buffer[end-5, i_body], buffer[end-4, i_body]
20692071
Da = FastMultipole.SVector{3}(Dax, Day, Daz)
@@ -2090,7 +2092,6 @@ function FastMultipole.body_to_multipole!(system::RigidWakeBody{Union{ConstantSo
20902092

20912093
# doublet strength
20922094
strength_doublet = FastMultipole.SVector{1,TF}(strength[2])
2093-
@show Da, Db, v1, v2, v1w, v2w, normal
20942095

20952096
# update values
20962097
element = FastMultipole.Panel{FastMultipole.Dipole}
@@ -2253,7 +2254,7 @@ function additional_source_system_to_buffer!(buffer, i_buffer, system::RigidWake
22532254
end
22542255

22552256
if !system.semiinfinite_wake
2256-
buffer[4] += update_radius # conservative update to accomadate wake size
2257+
buffer[4, i_buffer] += update_radius # conservative update to accomadate wake size
22572258
end
22582259

22592260
return ilast+8
@@ -2585,41 +2586,41 @@ end
25852586

25862587

25872588
##### INTERNAL FUNCTIONS ######################################################
2588-
function _get_wakestrength_mu(self::RigidWakeBody, i; stri=1)
2589+
function _get_wakestrength_mu(self::RigidWakeBody, i, isurf=1; stri=1)
25892590
# if self.use_wake_strength
25902591
# return self.wake_strength[i], zero(self.wake_strength[i])
25912592
# else
2592-
strength1 = self.strength[self.shedding[1, i], stri]
2593-
strength2 = self.shedding[4, i] != -1 ? self.strength[self.shedding[4, i], stri] : 0.0
2593+
strength1 = self.strength[self.shedding[isurf][1, i], stri]
2594+
strength2 = self.shedding[isurf][4, i] != -1 ? self.strength[self.shedding[isurf][4, i], stri] : 0.0
25942595
return strength1, strength2
25952596
# end
25962597
end
2597-
function _get_wakestrength_mu(self::RigidWakeBody{Union{ConstantSource,ConstantDoublet},2,<:Any}, i)
2598+
function _get_wakestrength_mu(self::RigidWakeBody{Union{ConstantSource,ConstantDoublet},2,<:Any}, i, isurf=1)
25982599
# if self.use_wake_strength
25992600
# return self.wake_strength[i], zero(self.wake_strength[i])
26002601
# else
26012602
stri = 2
2602-
strength1 = self.strength[self.shedding[1, i], stri]
2603-
strength2 = self.shedding[4, i] != -1 ? self.strength[self.shedding[4, i], stri] : 0.0
2603+
strength1 = self.strength[self.shedding[isurf][1, i], stri]
2604+
strength2 = self.shedding[isurf][4, i] != -1 ? self.strength[self.shedding[isurf][4, i], stri] : 0.0
26042605
return strength1, strength2
26052606
# end
26062607
end
2607-
function _get_wakestrength_Gamma(self::RigidWakeBody, i; stri=1)
2608+
function _get_wakestrength_Gamma(self::RigidWakeBody, i, isurf=1; stri=1)
26082609
# if self.use_wake_strength
26092610
# return self.wake_strength[i]
26102611
# else
2611-
strength1 = self.strength[self.shedding[1, i], stri]
2612-
strength2 = self.shedding[4, i] != -1 ? self.strength[self.shedding[4, i], stri] : 0.0
2612+
strength1 = self.strength[self.shedding[isurf][1, i], stri]
2613+
strength2 = self.shedding[isurf][4, i] != -1 ? self.strength[self.shedding[isurf][4, i], stri] : 0.0
26132614
return strength1 - strength2
26142615
# end
26152616
end
2616-
function _get_wakestrength_Gamma(self::RigidWakeBody{Union{ConstantSource,ConstantDoublet},2,<:Any}, i)
2617+
function _get_wakestrength_Gamma(self::RigidWakeBody{Union{ConstantSource,ConstantDoublet},2,<:Any}, i, isurf=1)
26172618
# if self.use_wake_strength
26182619
# return self.wake_strength[i]
26192620
# else
26202621
stri = 2
2621-
strength1 = self.strength[self.shedding[1, i], stri]
2622-
strength2 = self.shedding[4, i] != -1 ? self.strength[self.shedding[4, i], stri] : 0.0
2622+
strength1 = self.strength[self.shedding[isurf][1, i], stri]
2623+
strength2 = self.shedding[isurf][4, i] != -1 ? self.strength[self.shedding[isurf][4, i], stri] : 0.0
26232624
return strength1 - strength2
26242625
# end
26252626
end
@@ -2733,3 +2734,72 @@ function _savewake(self::RigidWakeBody, filename::String;
27332734
end
27342735
end
27352736
#### END OF LIFTING BODY ######################################################
2737+
2738+
function write_vtk(name::String, body::RigidWakeBody; t=0.0, overwrite::Bool=false, semiinfinite_length=5.0)
2739+
# set wake length for visualization (if semi-infinite wake is enabled)
2740+
if body.semiinfinite_wake
2741+
for (Das, Dbs) in zip(body.Das, body.Dbs)
2742+
for i in 1:size(Das, 2)
2743+
# norm_Da = norm(view(Das, :, i))
2744+
# norm_Db = norm(view(Dbs, :, i))
2745+
Das[:, i] .*= semiinfinite_length # / norm_Da
2746+
Dbs[:, i] .*= semiinfinite_length # / norm_Db
2747+
end
2748+
end
2749+
end
2750+
2751+
WriteVTK.paraview_collection(name; append=!overwrite) do pvd
2752+
vtm = WriteVTK.vtk_multiblock(name*"_wake")
2753+
for i_surf in eachindex(body.shedding)
2754+
shedding = body.shedding[i_surf]
2755+
Das = body.Das[i_surf]
2756+
Dbs = body.Dbs[i_surf]
2757+
2758+
n_wakes = size(shedding, 2)
2759+
points = zeros(typeof(body.grid._nodes[1]), 3, 4 * n_wakes)
2760+
cells = Vector{WriteVTK.MeshCell}(undef, n_wakes)
2761+
strengths = zeros(typeof(body.strength[1]), n_wakes)
2762+
2763+
for i in 1:n_wakes
2764+
pi = shedding[1, i]
2765+
nia, nib = shedding[2, i], shedding[3, i]
2766+
2767+
idx1 = body.cells[nia, pi]
2768+
idx2 = body.cells[nib, pi]
2769+
2770+
p1 = body.grid._nodes[:, idx1]
2771+
p2 = body.grid._nodes[:, idx2]
2772+
2773+
p3 = p2 + Dbs[:, i]
2774+
p4 = p1 + Das[:, i]
2775+
2776+
points[:, 1 + 4*(i-1)] = p1
2777+
points[:, 2 + 4*(i-1)] = p2
2778+
points[:, 3 + 4*(i-1)] = p3
2779+
points[:, 4 + 4*(i-1)] = p4
2780+
2781+
cells[i] = WriteVTK.MeshCell(WriteVTK.VTKCellTypes.VTK_QUAD, [1, 2, 3, 4] .+ 4*(i-1))
2782+
2783+
mu_upper, mu_lower = _get_wakestrength_mu(body, i)
2784+
strengths[i] = mu_upper - mu_lower
2785+
end
2786+
2787+
WriteVTK.vtk_grid(vtm, name * "_wake_$i_surf", points, cells) do vtk
2788+
vtk["mu"] = strengths
2789+
end
2790+
end
2791+
pvd[t] = vtm
2792+
end
2793+
2794+
# restore original values of Da and Db if they were modified for visualization
2795+
if body.semiinfinite_wake
2796+
for (Das, Dbs) in zip(body.Das, body.Dbs)
2797+
for i in 1:size(Das, 2)
2798+
# norm_Da = norm(view(Das, :, i))
2799+
# norm_Db = norm(view(Dbs, :, i))
2800+
Das[:, i] ./= semiinfinite_length # / norm_Da
2801+
Dbs[:, i] ./= semiinfinite_length # / norm_Db
2802+
end
2803+
end
2804+
end
2805+
end

src/FLOWPanel_simulate.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ function simulate!(system::AbstractBody{TK,NK,TF}, wake::PanelWake, frames#=::Ab
121121

122122
add_field(system, "U", "vector", collect(eachcol(system.velocity)), "cell")
123123
if has_grad_mu(system)
124-
pnl.calcfield_Ugradmu(body; Gammai=get_Gammai(system))
125-
add_fields(system, "Ugradmu", "U")
124+
calcfield_Ugradmu(system; Gammai=get_Gammai(system))
125+
addfields(system, "Ugradmu", "U")
126126
end
127-
pnl.calcfield_Cp(body, norm(uinf))
128-
pnl.calcfield_F(body, norm(uinf), rho)
127+
calcfield_Cp(system, norm(uinf))
128+
calcfield_F(system, norm(uinf), rho)
129129

130130
#------- other solvers -------#
131131

@@ -138,12 +138,12 @@ function simulate!(system::AbstractBody{TK,NK,TF}, wake::PanelWake, frames#=::Ab
138138

139139
if !isnothing(path)
140140
# panel body
141-
# write_vtk(joinpath(path, name * "_step_$i_step"), system; vtk_args...) # trailing_edge_list=.!shedding_surfaces, vtk_args...)
141+
save(system, joinpath(path, name * "_vehicle_step_$i_step"))
142142

143143
# particle field
144144

145145
# visualize wake
146-
write_vtk(name, wake, i_step, t)
146+
write_vtk(joinpath(path, name), wake, i_step, t)
147147

148148
end
149149

src/FLOWPanel_solver.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,19 +283,25 @@ struct FGSSolver{TFGS,TF<:Number} <: AbstractMatrixFreeSolver
283283
leaf_size::Int
284284
multipole_acceptance::Float64
285285
max_iterations::Int
286+
inner_iterations::Int
286287
tolerance::Float64
287288
rlx::Float64
289+
reverse_pass::Bool
290+
verbose::Bool
288291
end
289292

290293
function FGSSolver(body::AbstractBody;
291294
max_iterations::Int=100, # Maximum number of iterations
295+
inner_iterations::Int=1,
292296
tolerance::Real=1e-6, # Convergence tolerance
293-
rlx::Real=1.0, # Relaxation factor
297+
rlx::Real=1.0, # Relaxation factor
298+
reverse_pass::Bool=true, # Whether to perform reverse pass for adjoint sensitivities
294299
expansion_order=7,
295300
multipole_acceptance=0.4,
296301
leaf_size=10,
297302
shrink=false,
298303
recenter=false,
304+
verbose=false,
299305
)
300306

301307
# ensure CPoffset is negative (we'll solve this in the interior)
@@ -309,7 +315,7 @@ function FGSSolver(body::AbstractBody;
309315
# restore CPoffset
310316
body.CPoffset = CPoffset_old
311317

312-
return FGSSolver{typeof(fgs), TF}(fgs, Int(expansion_order), Int(leaf_size), Float64(multipole_acceptance), max_iterations, Float64(tolerance), Float64(rlx))
318+
return FGSSolver{typeof(fgs), TF}(fgs, Int(expansion_order), Int(leaf_size), Float64(multipole_acceptance), max_iterations, Int(inner_iterations), Float64(tolerance), Float64(rlx), Bool(reverse_pass), Bool(verbose))
313319
end
314320

315321
#--- test solve! ---#
@@ -326,7 +332,7 @@ function solve2!(self::AbstractBody{<:Any,1,<:Any}, Uinfs::Array{<:Real, 2}, sol
326332
self.velocity .= Uinfs
327333

328334
# solve system
329-
FastMultipole.solve!(self, solver.fgs; max_iterations=solver.max_iterations, tolerance=solver.tolerance, rlx=solver.rlx)
335+
FastMultipole.solve!(self, solver.fgs; max_iterations=solver.max_iterations, inner_iterations=solver.inner_iterations, tolerance=solver.tolerance, rlx=solver.rlx, verbose=solver.verbose)
330336

331337
# store solution
332338
set_solution(self, self.strength, self.strength, Tuple{Int,Float64}[], Uinfs)

src/FLOWPanel_wake.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ function solve!(body::AbstractBody{TB}, wake::AbstractFreeWake, uinf::AbstractAr
4444
end
4545

4646
# solve body
47-
check_nans(body)
48-
@show true in body_probes.gradient
49-
@show true in body_probes.scalar_potential
5047
solve2!(body, body_probes.gradient, body_solver; backend)
5148

5249
# body-on-all influence
@@ -110,7 +107,9 @@ function get_probes(body::AbstractBody{TK,NK,TF}) where {TK,NK,TF}
110107
normals = _calc_normals(body)
111108
CPs = _calc_controlpoints(body, normals; off=-1e-10)
112109
@assert !requires_hessian(body) "`get_probes` must be overloaded to support Hessian output for body type $(typeof(body))"
113-
body_probes = FastMultipole.ProbeSystemArray(CPs, body.potential, body.velocity, hessian)
110+
potential = zeros(TF, size(CPs, 2))
111+
velocity = zeros(TF, 3, size(CPs, 2))
112+
body_probes = FastMultipole.ProbeSystemArray(CPs, potential, velocity, hessian)
114113
return body_probes
115114
end
116115

0 commit comments

Comments
 (0)