Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vortex ring example simulation #16

Merged
merged 3 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 291 additions & 0 deletions scripts/example-vortexring.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
#=##############################################################################
# DESCRIPTION
Vortex ring simulation.

This module contains multiple functions that have been copied/pasted
from FLOWVPM by the author. See
https://github.com/byuflowlab/FLOWVPM.jl/tree/master/examples/vortexrings

# AUTHORSHIP
* Author : Eduardo J. Alvarez
* Email : [email protected]
* Created : Nov 9th, 2024
=###############################################################################


import Elliptic
import Roots
import Cubature
import LinearAlgebra: I, norm, cross

modulepath = splitdir(@__FILE__)[1] # Path to this module

# Load FastMultipole `vortex.jl` module
using StaticArrays
include(joinpath(modulepath, "..", "test", "vortex.jl"))




# -------------- USEFUL FUNCTIONS ----------------------------------------------
"Number of particles used to discretized a ring"
number_particles(Nphi, nc; extra_nc=0) = Int( Nphi * ( 1 + 8*sum(1:(nc+extra_nc)) ) )

"Analytic self-induced velocity of an inviscid ring"
Uring(circulation, R, Rcross, beta) = circulation/(4*pi*R) * ( log(8*R/Rcross) - beta )

"""
`addvortexring(pfield, circulation, R, AR, Rcross, Nphi, nc, sigma;
extra_nc=0, O=zeros(3), Oaxis=eye(3))`

Adds a vortex ring to the particle field `pfield`. The ring is discretized as
described in Winckelmans' 1989 doctoral thesis (Topics in Vortex Methods...),
where the ring is an ellipse of equivalent radius `R=sqrt(a*b)`, aspect ratio
`AR=a/b`, and cross-sectional radius `Rcross`, where `a` and `b` are the
semi-major and semi-minor axes, respectively. Hence, `AR=1` defines a circle of
radius `R`.

The ring is discretized with `Nphi` cross section evenly spaced and the
thickness of the toroid is discretized with `nc` layers, using particles with
smoothing radius `sigma`. Here, `nc=0` means that the ring is represented only
with particles centered along the centerline, and `nc>0` is the number of layers
around the centerline extending out from 0 to `Rcross`.

Additional layers of empty particles (particle with no strength) beyond `Rcross`
can be added with the optional argument `extra_nc`.

The ring is placed in space at the position `O` and orientation `Oaxis`,
where `Oaxis[:, 1]` is the major axis, `Oaxis[:, 2]` is the minor axis, and
`Oaxis[:, 3]` is the line of symmetry.
"""
function vortexring(circulation::Real,
R::Real, AR::Real, Rcross::Real,
Nphi::Int, nc::Int, sigma::Real; extra_nc::Int=0,
O::Vector{<:Real}=zeros(3), Oaxis=I,
verbose=true, v_lvl=0
)

maxnp = number_particles(Nphi, nc; extra_nc=extra_nc)
pfield = zeros(7, maxnp)

# ERROR CASE
if AR < 1
error("Invalid aspect ratio AR < 1 (AR = $(AR))")
end

a = R*sqrt(AR) # Semi-major axis
b = R/sqrt(AR) # Semi-minor axis

fun_S(phi, a, b) = a * Elliptic.E(phi, 1-(b/a)^2) # Arc length from 0 to a given angle
Stot = fun_S(2*pi, a, b) # Total perimeter length of centerline

# Non-dimensional arc length from 0 to a given value <=1
fun_s(phi, a, b) = fun_S(phi, a, b)/fun_S(2*pi, a, b)
# Angle associated to a given non-dimensional arc length
fun_phi(s, a, b) = abs(s) <= eps() ? 0 :
abs(s-1) <= eps() ? 2*pi :
Roots.fzero( phi -> fun_s(phi, a, b) - s, (0, 2*pi-eps()), atol=1e-16, rtol=1e-16)

# Length of a given filament in a
# cross section cell
function fun_length(r, tht, a, b, phi1, phi2)
S1 = fun_S(phi1, a + r*cos(tht), b + r*cos(tht))
S2 = fun_S(phi2, a + r*cos(tht), b + r*cos(tht))

return S2-S1
end
# Function to be integrated to calculate
# each cell's volume
function fun_dvol(r, args...)
return r * fun_length(r, args...)
end
# Integrate cell volume
function fun_vol(dvol_wrap, r1, tht1, r2, tht2)
(val, err) = Cubature.hcubature(dvol_wrap, [r1, tht1], [r2, tht2];
reltol=1e-8, abstol=0, maxevals=1000)
return val
end

invOaxis = inv(Oaxis) # Add particles in the global coordinate system
np = 0
function addparticle(pfield, X, Gamma, sigma, vol, circulation)
X_global = Oaxis*X + O
Gamma_global = Oaxis*Gamma

np += 1
pfield[1:3, np] .= X_global
pfield[4, np] = sigma
pfield[5:7, np] .= Gamma_global

end

rl = Rcross/(2*nc + 1) # Radial spacing between cross-section layers
dS = Stot/Nphi # Perimeter spacing between cross sections
ds = dS/Stot # Non-dimensional perimeter spacing

omega = circulation / (pi*Rcross^2) # Average vorticity

org_np = 0

# Discretization of torus into cross sections
for N in 0:Nphi-1

# Non-dimensional arc-length position of cross section along centerline
sc1 = ds*N # Lower bound
sc2 = ds*(N+1) # Upper bound
sc = (sc1 + sc2)/2 # Center

# Angle of cross section along centerline
phi1 = fun_phi(sc1, a, b) # Lower bound
phi2 = fun_phi(sc2, a, b) # Upper bound
phic = fun_phi(sc, a, b) # Center

Xc = [a*sin(phic), b*cos(phic), 0] # Center of the cross section
T = [a*cos(phic), -b*sin(phic), 0] # Unitary tangent of this cross section
T ./= norm(T)
T .*= -1 # Flip to make +circulation travel +Z
# Local coordinate system of section
Naxis = hcat(T, cross([0,0,1], T), [0,0,1])

# Volume of each cell in the cross section
dvol_wrap(X) = fun_dvol(X[1], X[2], a, b, phi1, phi2)


# Discretization of cross section into layers
for n in 0:nc+extra_nc

if n==0 # Particle in the center

r1, r2 = 0, rl # Lower and upper radius
tht1, tht2 = 0, 2*pi # Left and right angle
vol = fun_vol(dvol_wrap, r1, tht1, r2, tht2) # Volume
X = Xc # Position
Gamma = omega*vol*T # Vortex strength
# Filament length
length = fun_length(0, 0, a, b, phi1, phi2)
# Circulation
crcltn = norm(Gamma) / length

addparticle(pfield, X, Gamma, sigma, vol, crcltn)

else # Layers

rc = (1 + 12*n^2)/(6*n)*rl # Center radius
r1 = (2*n-1)*rl # Lower radius
r2 = (2*n+1)*rl # Upper radius
ncells = 8*n # Number of cells
deltatheta = 2*pi/ncells # Angle of cells

# Discretize layer into cells around the circumference
for j in 0:(ncells-1)

tht1 = deltatheta*j # Left angle
tht2 = deltatheta*(j+1) # Right angle
thtc = (tht1 + tht2)/2 # Center angle
vol = fun_vol(dvol_wrap, r1, tht1, r2, tht2) # Volume
# Position
X = Xc + Naxis*[0, rc*cos(thtc), rc*sin(thtc)]

# Vortex strength
if n<=nc # Ring particles
Gamma = omega*vol*T
else # Particles for viscous diffusion
Gamma = eps()*T
end
# Filament length
length = fun_length(rc, thtc, a, b, phi1, phi2)
# Circulation
crcltn = norm(Gamma) / length


addparticle(pfield, X, Gamma, sigma, vol, crcltn)
end

end

end

end

if verbose
println("\t"^(v_lvl)*"Number of particles: $(np - org_np)")
end

return pfield
end









# -------------- SIMULATION PARAMETERS -----------------------------------------
nsteps = 1000 # Number of time steps
Rtot = 2.0 # (m) run simulation for equivalent
# time to this many radii

nrings = 1 # Number of rings
nc = 1 # Number of toroidal layers of discretization
# nc = 0
dZ = 0.1 # (m) spacing between rings
circulations = 1.0*ones(nrings) # (m^2/s) circulation of each ring
Rs = 1.0*ones(nrings) # (m) radius of each ring
ARs = 1.0*ones(nrings) # Aspect ratio AR = a/r of each ring
Rcrosss = 0.15*Rs # (m) cross-sectional radii
sigmas = Rcrosss # Particle smoothing of each radius
Nphis = 100*ones(Int, nrings) # Number of cross sections per ring
ncs = nc*ones(Int, nrings) # Number layers per cross section
extra_ncs = 0*ones(Int, nrings) # Number of extra layers per cross section
Os = [[0, 0, dZ*(ri-1)] for ri in 1:nrings] # Position of each ring
Oaxiss = [I for ri in 1:nrings] # Orientation of each ring
nref = 1 # Reference ring

beta = 0.5 # Parameter for theoretical velocity
faux = 0.25 # Shrinks the discretized core by this factor

pfields = []

for ringi in 1:nrings
pfield = vortexring(circulations[ringi],
Rs[ringi], ARs[ringi], Rcrosss[ringi],
Nphis[ringi], ncs[ringi], sigmas[ringi]; extra_nc=extra_ncs[ringi],
O=Os[ringi], Oaxis=Oaxiss[ringi],
verbose=true, v_lvl=0
)
push!(pfields, pfield)
end

pfield = hcat(pfields...)

vortexparticles = VortexParticles(pfield);

# save_vtk("vortexring-000", vortexparticles)



# -------------- RUN SIMULATION ------------------------------------------------
Uref = Uring(circulations[nref], Rs[nref], Rcrosss[nref], beta) # (m/s) reference velocity
dt = (Rtot/Uref) / nsteps # (s) time step

# Create folder to save simulation
savepath = "example-vortexring"

if isdir(savepath)
rm(savepath, recursive=true)
end
mkpath(savepath)

convect!(vortexparticles, nsteps;
# integration options
integrate=Euler(dt),
# fmm options
fmm_p=4, fmm_ncrit=50, fmm_multipole_threshold=0.5,
# fmm_p=43, fmm_ncrit=50, fmm_multipole_threshold=0.5,
direct=false,
# direct=true,
# save options
save=true, filename=joinpath(savepath, "vortexring"), compress=false,
)
3 changes: 3 additions & 0 deletions test/fmm_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ bodies = [
0.08 0.5 1.1
]

Random.seed!(123)
bodies = rand(7,3)

vortex_particles = VortexParticles(bodies)

psis = zeros(3,3)
Expand Down
Loading
Loading