Viscous flow about moving bodies

In this notebook we will demonstrate the simulation of a system of moving bodies. As we will show, it is straightforward to set up a moving body, using the tools in RigidBodyTools.jl. The main caveat is that the simulation is slower, because the integrator must update the operators continuously throughout the simulation.

We will demonstrate this on a system of three linked plates undergoing a flapping motion, in which the middle plate heaves up and down, and the other two bodies pitch back and forth on hinges connecting their edges to the middle plate.

using ViscousFlow
using Plots

Problem specification and discretization

For simplicity, we will not create a free stream in this problem. Everything here is the usual.

my_params = Dict()
my_params["Re"] = 200
200
xlim = (-1.0,1.0)
ylim = (-1.0,1.0)
my_params["grid Re"] = 4.0
g = setup_grid(xlim,ylim,my_params)

Δs = surface_point_spacing(g,my_params)
0.027999999999999997

Set up body

Set up the plates.

Lp = 0.5
body1 = Plate(Lp,Δs)
body2 = Plate(Lp,Δs)
body3 = Plate(Lp,Δs)
bl = BodyList([body1,body2,body3])
BodyList(Body[Open polygon with 2 vertices and 18 points
   Current position: (0.0,0.0)
   Current angle (rad): 0.0
, Open polygon with 2 vertices and 18 points
   Current position: (0.0,0.0)
   Current angle (rad): 0.0
, Open polygon with 2 vertices and 18 points
   Current position: (0.0,0.0)
   Current angle (rad): 0.0
])

Set the body motions

Here, we make use of joints to prescribe the motion of every part of this system. We will attach body 1 to the inertial system, making it oscillate up and down. This is a special case of a joint with three degrees of freedom, called a FreeJoint2d. Bodies 2 and 3 will each be connected by hinges (i.e., with a RevoluteJoint) to body 1.

parent_body, child_body = 0, 1
Xp = MotionTransform([0,0],0) # location of joint in inertial system
xpiv = [0,0] # place center of motion at center of the plate
Xc = MotionTransform(xpiv,0)
2d motion transform, x = [0.0, 0.0], R = [1.0 -0.0; 0.0 1.0]

Now the motion for joint 1, which we set up through the three degrees of freedom. The first and second are meant to be fixed, so we give them zero velocity, and the third we assign oscillatory kinematics

adof = ConstantVelocityDOF(0.0)
xdof = ConstantVelocityDOF(0.0)

Ω = 1
A = 0.25  # amplitude/chord
ϕh = 0.0  # phase lag of heave
ydof = OscillatoryDOF(A,Ω,ϕh,0.0)

dofs = [adof,xdof,ydof]
3-element Vector{AbstractPrescribedDOFKinematics}:
 Constant velocity kinematics (velocity = 0.0)
 Constant velocity kinematics (velocity = 0.0)
 Oscillatory kinematics (amplitude = 0.25, ang freq = 1.0, phase = 0.0, mean velocity = 0.0)

Now assemble the joint

joint1 = Joint(FreeJoint2d,parent_body,Xp,child_body,Xc,dofs)
Joint of dimension 2 and type FreeJoint2d
   Constrained dofs = [1, 2, 3]
   Exogenous dofs = Int64[]
   Unconstrained dofs = Int64[]

Now the two hinges. Each of these is a RevoluteJoint. We place them at either end of body 1. Joint 2 between bodies 1 and 2

parent_body, child_body = 1, 2
Xp = MotionTransform([0.25,0],0) # right side of body 1
Xc = MotionTransform([-0.25,0],0) # left side of body 2
Δα = 20π/180 # amplitude of pitching
ϕp = π/2 # phase lead of pitch
θdof = OscillatoryDOF(Δα,Ω,ϕp,0.0)
joint2 = Joint(RevoluteJoint,parent_body,Xp,child_body,Xc,[θdof])
Joint of dimension 2 and type RevoluteJoint
   Constrained dofs = [1]
   Exogenous dofs = Int64[]
   Unconstrained dofs = Int64[]

and joint 3 between bodies 1 and 3

parent_body, child_body = 1, 3
Xp = MotionTransform([-0.25,0],0) # left side of body 1
Xc = MotionTransform([0.25,0],0) # right side of body 3
ϕp = -π/2 # phase lead of pitch, but lagging rather than leading
θdof = OscillatoryDOF(Δα,Ω,ϕp,0.0)
joint3 = Joint(RevoluteJoint,parent_body,Xp,child_body,Xc,[θdof])
Joint of dimension 2 and type RevoluteJoint
   Constrained dofs = [1]
   Exogenous dofs = Int64[]
   Unconstrained dofs = Int64[]

Assemble everything together

m = RigidBodyMotion([joint1,joint2,joint3],bl)
1 linked system(s) of bodies
   3 bodies
   3 joints

We generate the initial joint state vector with `init_and update the body system and plot it

x = init_motion_state(bl,m)
update_body!(bl,x,m)
plot(bl,xlim=xlim,ylim=ylim)
Example block output

Here is a useful macro to visualize the motion as a movie:

macro animate_motion(b,m,dt,tmax,xlim,ylim)
    return esc(quote
            bc = deepcopy($b)
            t0, x0 = 0.0, init_motion_state(bc,$m)
            dxdt = zero(x0)
            x = copy(x0)

            @gif for t in t0:$dt:t0+$tmax
                motion_rhs!(dxdt,x,($m,bc),t)
                global x += dxdt*$dt
                update_body!(bc,x,$m)
                plot(bc,xlim=$xlim,ylim=$ylim)
            end every 5
        end)
end

Define the boundary condition functions

Instead of using the default boundary condition functions, we define special ones here that provide the instantaneous surface velocity (i.e. the velocity of every surface point) from the prescribed motion. Every surface has an "exterior" and "interior" side. For a flat plate, these two sides are the upper and lower sides, and both sides are next to the fluid, so both of them are assigned the prescribed velocity of the plate. (For closed bodies, we would assign this velocity to only one of the sides, and zero to the other side. We will see an example of this in a later case.) We pack these into a special dictionary and pass these to the system construction.

function my_vsplus(t,x,base_cache,phys_params,motions)
  vsplus = zeros_surface(base_cache)
  surface_velocity!(vsplus,x,base_cache,motions,t)
  return vsplus
end

function my_vsminus(t,x,base_cache,phys_params,motions)
  vsminus = zeros_surface(base_cache)
  surface_velocity!(vsminus,x,base_cache,motions,t)
  return vsminus
end

bcdict = Dict("exterior" => my_vsplus, "interior" => my_vsminus)
Dict{String, Function} with 2 entries:
  "interior" => my_vsminus
  "exterior" => my_vsplus

Construct the system structure

Here, we supply both the motion and boundary condition functions as additional arguments.

sys = viscousflow_system(g,bl,phys_params=my_params,motions=m,bc=bcdict);

and generate the initial condition

u0 = init_sol(sys)
(Dual nodes in a (nx = 104, ny = 104) cell grid of type Float64 data
  Number of Dual nodes: (nx = 104, ny = 104), [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, -0.3490658503988659, 0.3490658503988659])

Before we solve the problem, it is useful to note that the Reynolds number we specified earlier may not be the most physically-meaningful Reynolds number. More relevant in this problem is the Reynolds number based on the maximum body speed and the total length of the plates

Umax, imax, tmax, bmax = maxvelocity(u0,sys)
L = 3*Lp
Re_eff = my_params["Re"]*Umax*L
89.45672121102322

In other problems, we have stored the inverse of the matrix system that we have to solve at each time stage. But since that matrix system changes as the body moves, it is faster here to use an iterative solver (congjugate gradient)

tspan = (0.0,10.0)
integrator = init(u0,tspan,sys,alg=LiskaIFHERK(saddlesolver=CG))
t: 0.0
u: (Dual nodes in a (nx = 104, ny = 104) cell grid of type Float64 data
  Number of Dual nodes: (nx = 104, ny = 104), [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, -0.3490658503988659, 0.3490658503988659])

Solve

This takes a bit longer per time step than it does for stationary bodies. Here, we will advance it one time step (which also compiles the code), then only run it for 1.5 time units just to demonstrate it.

step!(integrator)
@time step!(integrator,1.5)
 16.067751 seconds (10.57 M allocations: 6.403 GiB, 23.15% gc time, 1.41% compilation time)

Examine the solution

Let's look at a few snapshots of the vorticity field. Note that the plotting here requires us to explicitly call the surfaces function to generate the instantaneous configuration of the plate.

sol = integrator.sol
plt = plot(layout = (1,3), size = (800, 300), legend=:false)
tsnap = 0.5:0.5:1.5
for (i, t) in enumerate(tsnap)
    plot!(plt[i],vorticity(sol,sys,t),sys,layers=false,title="t = $(round(t,digits=2))",clim=(-5,5),levels=range(-5,5,length=30),color = :RdBu)
    plot!(plt[i],surfaces(sol,sys,t))
end
plt
Example block output

and the forces and moments

sol = integrator.sol
mom, fx, fy = force(sol,sys,1);
plot(
plot(sol.t,2*fx,xlim=(0,Inf),ylim=(-2,2),xlabel="Convective time",ylabel="\$C_D\$",legend=:false),
plot(sol.t,2*fy,xlim=(0,Inf),ylim=(-2,2),xlabel="Convective time",ylabel="\$C_L\$",legend=:false),
    size=(800,350)
)
Example block output

This page was generated using Literate.jl.