Skip to content

Commit 2d41ba1

Browse files
authored
Merge pull request #266 from marinlauber/RigidBodies
Add `RigidMap` for fluid-(rigid)body interaction
2 parents 8a0e043 + db604d4 commit 2d41ba1

File tree

5 files changed

+119
-14
lines changed

5 files changed

+119
-14
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["Gabriel Weymouth <gabriel.weymouth@gmail.com>"]
44
version = "1.5.2"
55

66
[deps]
7+
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
78
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
89
EllipsisNotation = "da5c29d0-fa7d-589e-88eb-ea29b0a81949"
910
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -38,6 +39,7 @@ WaterLilyReadVTKExt = "ReadVTK"
3839
WaterLilyWriteVTKExt = "WriteVTK"
3940

4041
[compat]
42+
ConstructionBase = "1.6.0"
4143
DocStringExtensions = "0.9"
4244
EllipsisNotation = "1.8"
4345
ForwardDiff = "0.10.18, 1"

src/AutoBody.jl

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
"""
2-
AutoBody(sdf,map=(x,t)->x; compose=true) <: AbstractBody
2+
AutoBody(sdf,map=(x,t)->x) <: AbstractBody
33
44
- `sdf(x::AbstractVector,t::Real)::Real`: signed distance function
55
- `map(x::AbstractVector,t::Real)::AbstractVector`: coordinate mapping function
6-
- `compose::Bool=true`: Flag for composing `sdf=sdf∘map`
76
87
Implicitly define a geometry by its `sdf` and optional coordinate `map`. Note: the `map`
9-
is composed automatically if `compose=true`, i.e. `sdf(x,t) = sdf(map(x,t),t)`.
10-
Both parameters remain independent otherwise. It can be particularly heplful to set
11-
`compose=false` when adding mulitple bodies together to create a more complex one.
8+
is composed automatically i.e. `sdf(body::AutoBody,x,t) = body.sdf(body.map(x,t),t)`.
129
"""
1310
struct AutoBody{F1<:Function,F2<:Function} <: AbstractBody
1411
sdf::F1
1512
map::F2
16-
function AutoBody(sdf, map=(x,t)->x; compose=true)
17-
comp(x,t) = compose ? sdf(map(x,t),t) : sdf(x,t)
18-
new{typeof(comp),typeof(map)}(comp, map)
19-
end
2013
end
14+
AutoBody(sdf, map=(x,t)->x) = AutoBody(sdf, map)
2115

2216
"""
23-
d = sdf(body::AutoBody,x,t) = body.sdf(x,t)
17+
d = sdf(body::AutoBody,x,t) = body.sdf(body.map(x,t),t)
2418
"""
25-
sdf(body::AutoBody,x,t=0;kwargs...) = body.sdf(x,t)
19+
@inline sdf(body::AutoBody,x,t=0;kwargs...) = body.sdf(body.map(x,t),t)
2620

2721
using ForwardDiff
2822
"""
@@ -35,9 +29,9 @@ Skips the `n,V` calculation when `d²>fastd²`.
3529
"""
3630
function measure(body::AutoBody,x,t;fastd²=Inf)
3731
# eval d=f(x,t), and n̂ = ∇f
38-
d = body.sdf(x,t)
32+
d = sdf(body,x,t)
3933
d^2>fastd² && return (d,zero(x),zero(x)) # skip n,V
40-
n = ForwardDiff.gradient(x->body.sdf(x,t), x)
34+
n = ForwardDiff.gradient(x->sdf(body,x,t), x)
4135
any(isnan.(n)) && return (d,zero(x),zero(x))
4236

4337
# correct general implicit fnc f(x₀)=0 to be a pseudo-sdf

src/RigidMap.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
RigidMap(center, θ) <: AbstractBody
3+
4+
- `x₀::SVector{D}`: coordinate of the center of the body
5+
- `θ::Union{Real, SVector{3}}`: rotation (single angle in 2D, and in 3D these are the rotation angle around
6+
the x, y, and z axes respectively.)
7+
- `V::SVector{D}=zero(center)`: linear velocity of the center
8+
- `xₚ::SVector{D}=zero(center)`: offset of the pivot point compared to center
9+
- `ω::Union{Real, SVector{3}}=zero(θ)`: angular velocity (scalar in 2D, vector in 3D)
10+
11+
Define a `RigidMap` for any `AbstractBody` using rigid body motion parameters.
12+
13+
RigidMap updates are computed externally via a set of ODEs and then updated in the
14+
simulation loop following:
15+
```julia
16+
using WaterLily,StaticArrays
17+
body = AutoBody((x,t)->sqrt(sum(abs2,x))-4,RigidMap(SA{Float32}[16,16],0.f0;ω=0.1f0))
18+
sim = Simulation((32,32),(1,0),8;body)
19+
for n in 1:10
20+
# update body motion (example: constant angular velocity)
21+
θ = sim.body.map.θ + sim.body.map.ω*sim.flow.Δt[end]
22+
sim.body = setmap(sim.body; θ)
23+
# remeasure and step
24+
sim_step!(sim;remeasure=true)
25+
end
26+
```
27+
"""
28+
struct RigidMap{A<:AbstractVector,R} <: Function
29+
x₀ :: A # center of translation
30+
θ :: R # rotation (angle in 2D, euler angles in 3D)
31+
xₚ :: A # rotation offset
32+
V :: A # linear velocity of the center
33+
ω :: R # angular velocity (scalar in 2D, vector in 3D)
34+
end
35+
RigidMap(x₀::SVector,θ;xₚ=zero(x₀),V=zero(x₀),ω=zero(θ)) = RigidMap(x₀, θ, xₚ, V, ω)
36+
37+
# this is the function map(x,t) AND derivative(t->map(x,t),t)
38+
(map::RigidMap)(x::SVector,t=0) = rotation(map.θ)*(x-map.x₀-map.xₚ)+map.xₚ
39+
(map::RigidMap)(x::SVector,::ForwardDiff.Dual{Tag}) where Tag = Dual{Tag}.(map(x),-rotation(map.θ)*(map.V+map.ω×(x-map.x₀-map.xₚ)))
40+
41+
# cross product in 2D and rotation matrix in 2D and 3D
42+
import WaterLily: ×
43+
×(a::Number,b::SVector{2,T}) where T = a*SA[-b[2],b[1]]
44+
rotation::T) where T = SA{T}[cos(θ) sin(θ); -sin(θ) cos(θ)]
45+
rotation::SVector{3,T}) where T = SA{T}[cos(θ[3])*cos(θ[2]) cos(θ[3])*sin(θ[2])*sin(θ[1])+sin(θ[3])*cos(θ[1]) -cos(θ[3])*sin(θ[2])*cos(θ[1])+sin(θ[3])*sin(θ[1]);
46+
-sin(θ[3])*cos(θ[2]) -sin(θ[3])*sin(θ[2])*sin(θ[1])+cos(θ[3])*cos(θ[1]) sin(θ[3])*sin(θ[2])*cos(θ[1])+cos(θ[3])*sin(θ[1]);
47+
sin(θ[2]) -cos(θ[2])*sin(θ[1]) cos(θ[2])*cos(θ[1])]
48+
import ConstructionBase: setproperties
49+
setmap(body::AbstractBody; kwargs...) = setproperties(body,map=setproperties(body.map; kwargs...))
50+
setmap(body::SetBody; kwargs...) = SetBody(body.op,setmap(body.a; kwargs...),setmap(body.b; kwargs...))
51+
setmap(body::NoBody; kwargs...) = NoBody()

src/WaterLily.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ include("Metrics.jl")
3030
export MeanFlow,update!,uu!,uu
3131

3232
abstract type AbstractSimulation end
33+
34+
include("RigidMap.jl")
35+
export RigidMap,setmap
36+
3337
"""
3438
Simulation(dims::NTuple, uBC::Union{NTuple,Function}, L::Number;
3539
U=norm2(Uλ), Δt=0.25, ν=0., ϵ=1, g=nothing,

test/maintests.jl

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ end
257257
body = AutoBody(circ)
258258
for i in 2:20
259259
body += AutoBody(circ,(x,t)->x-rand(2))
260-
@test sizeof(body) == i
260+
@test sizeof(body) i
261261
end
262262

263263
# test curvature, 2D and 3D
@@ -596,4 +596,58 @@ end
596596
@test all(meanflow1.t .== meanflow2.t)
597597
end
598598
@test_nowarn rm(test_dir, recursive=true)
599+
end
600+
@testset "RigidMap.jl" begin
601+
for T (Float32,Float64)
602+
# initialize a rigid body
603+
sdf(x,t) = sqrt(sum(abs2,x))-1
604+
body = AutoBody(sdf, RigidMap(SA{T}[0,0],T(0)))
605+
# check sdf
606+
@test all(measure(body,SA{T}[1.5,0],0) .≈ (1/2,SA{T}[1,0],SA{T}[0,0]))
607+
# rotate and add linear velocity
608+
body = setmap(body;θ=T/4),V=SA{T}[1.0,0])
609+
# check sdf and velocity
610+
@test all(measure(body,SA{T}[1.5,0],0) .≈ (1/2,SA{T}[1,0],SA{T}[1,0]))
611+
# add angular velocity
612+
body = setmap(body;ω=T(0.1))
613+
@test all(measure(body,SA{T}[1.5,0],0) .≈ (1/2,SA{T}[1,0],SA{T}[1,1.5*0.1]))
614+
# 3D rigid body
615+
body3D = AutoBody(sdf, RigidMap(SA{T}[0,0,0],SA{T}[0,0,0];xₚ=SA{T}[-.5,0,0]))
616+
@test all(measure(body3D,SA{T}[1.5,0,0],0) .≈ (1/2,SA{T}[1,0,0],SA{T}[0,0,0]))
617+
# test rotations about x, y, and z
618+
# rotate by 180 degrees about x-axis, should not change
619+
body3D = setmap(body3D;θ=SA{T}[π,0,0])
620+
@test all(measure(body3D,SA{T}[1.5,0,0],0) .≈ (1/2,SA{T}[1,0,0],SA{T}[0,0,0]))
621+
# now rotate by 180 around y=axis, should invert z-component of normal
622+
body3D = setmap(body3D;θ=SA{T}[0,π,0],V=SA{T}[1.0,0,0])
623+
@test all(measure(body3D,SA{T}[1.5,0,0],0) .≈ (1.5,SA{T}[1,0,0],SA{T}[1,0,0]))
624+
body3D = setmap(body3D;θ=SA{T}[0,0,π],V=SA{T}[1.0,0,0])
625+
@test all(measure(body3D,SA{T}[1.5,0,0],0) .≈ (1.5,SA{T}[1,0,0],SA{T}[1,0,0]))
626+
# 3D rigid body with linear and angular velocity
627+
body3D = setmap(body3D;θ=SA{T}[0,0,0],V=SA{T}[1.0,0,0],ω=SA{T}[0,0,0.1])
628+
@test all(measure(body3D,SA{T}[1.5,0,0],0) .≈ (1/2,SA{T}[1,0,0],SA{T}[1,0.2,0]))
629+
@test all(measure(body3D,SA{T}[0,1.5,0],0) .≈ (1/2,SA{T}[0,1,0],SA{T}[0.85,0.05,0]))
630+
@test all(measure(body3D,SA{T}[1.5,1.5,1.5],0) .≈ ((3*(1.5^2))-1,SA{T}[(1/3),(1/3),(1/3)],SA{T}[.85,0.2,0]))
631+
# three 3D rotations
632+
body3D = setmap(body3D;V=SA{T}[1.0,0,0],ω=SA{T}[0,-0.1,0.1])
633+
@test all(measure(body3D,SA{T}[1.5,0,0],0) .≈ (1/2,SA{T}[1,0,0],SA{T}[1,0.2,0.2]))
634+
@test all(measure(body3D,SA{T}[0,1.5,1.5],0) .≈ ((2*(1.5^2))-1,SA{T}[0,(1/2),(1/2)],SA{T}[0.7,0.05,0.05]))
635+
# test for a SetMap
636+
body = AutoBody(sdf, RigidMap(SA{T}[0,0],T(0))) +AutoBody(sdf, RigidMap(SA{T}[1,1],T(0)))
637+
body = setmap(body;θ=T/4),V=SA{T}[1.0,0])
638+
@test all(body.a.map.θ == body.b.map.θ == T/4))
639+
@test all(body.a.map.V .≈ body.b.map.V .≈ [1,0])
640+
# try measure in the sim using different backends
641+
for array in arrays
642+
body = AutoBody((x,t)->sqrt(sum(abs2,x))-4,RigidMap(SA{T}[16,16,16],SA{T}[0,0,0];
643+
V=SA{T}[0,0,0],ω=SA{T}[0,-0.1,0.1]))
644+
sim = Simulation((32,32,32),(1,0,0),8;body,T,mem=array)
645+
@test GPUArrays.@allowscalar all(extrema(sim.flow.V) .≈ (-0.9,0.9))
646+
sim.body = setmap(sim.body;x₀=SA{T}[16,16,12])
647+
@test GPUArrays.@allowscalar all(sim.flow.μ₀[17,17,17,:] .≈ 0)
648+
end
649+
end
650+
rmap = RigidMap(SA[0.,0.],π/4)
651+
body = AutoBody((x,t)->(x'x)-1,rmap)-AutoBody((x,t)->(x'x)-0.5,rmap) # annulus
652+
@test all(measure(setmap(body,ω=1.),SA[0.25,0.],0) .≈ (0.25,SA[-1,0],SA[0,0.25]))
599653
end

0 commit comments

Comments
 (0)