Git Product home page Git Product logo

waterlily.jl's Introduction

WaterLily.jl

Dev CI codecov

Julia flow

Overview

WaterLily.jl is a simple and fast fluid simulator written in pure Julia. This project is supported by awesome libraries developed within the Julia scientific community, and it aims to accelerate and enhance fluid simulations. Watch the JuliaCon2024 talk here:

JuliaCon2024 still and link

If you have used WaterLily for research, please cite us! The following manuscript describes the main features of the solver and provides benchmarking, validation, and profiling results.

@misc{WeymouthFont2024,
    title         = {WaterLily.jl: A differentiable and backend-agnostic Julia solver to simulate incompressible viscous flow and dynamic bodies},
    author        = {Gabriel D. Weymouth and Bernat Font},
    url           = {https://arxiv.org/abs/2407.16032},
    eprint        = {2407.16032},
    archivePrefix = {arXiv},
    year          = {2024},
    primaryClass  = {physics.flu-dyn}
}

Method/capabilities

WaterLily solves the unsteady incompressible 2D or 3D Navier-Stokes equations on a Cartesian grid. The pressure Poisson equation is solved with a geometric multigrid method. Solid boundaries are modelled using the Boundary Data Immersion Method. The solver can run on serial CPU, multi-threaded CPU, or GPU backends.

Examples

The user can set the boundary conditions, the initial velocity field, the fluid viscosity (which determines the Reynolds number), and immerse solid obstacles using a signed distance function. These examples and others are found in the examples directory.

Flow over a circle

We define the size of the simulation domain as n$\times$m cells. The circle has radius m/8 and is centered at (m/2,m/2). The flow boundary conditions are (U,0), where we set U=1, and the Reynolds number is Re=U*radius/ν where ν (Greek "nu" U+03BD, not Latin lowercase "v") is the kinematic viscosity of the fluid.

using WaterLily
function circle(n,m;Re=250,U=1)
    radius, center = m/8, m/2
    body = AutoBody((x,t)->sum(abs2, x .- center) - radius)
    Simulation((n,m), (U,0), radius; ν=U*radius/Re, body)
end

The second to last line defines the circle geometry using a signed distance function. The AutoBody function uses automatic differentiation to infer the other geometric parameter automatically. Replace the circle's distance function with any other, and now you have the flow around something else... such as a donut or the Julia logo. Finally, the last line defines the Simulation by passing in parameters we've defined.

Now we can create a simulation (first line) and run it forward in time (third line)

circ = circle(3*2^6,2^7)
t_end = 10
sim_step!(circ,t_end)

Note we've set n,m to be multiples of powers of 2, which is important when using the (very fast) geometric multi-grid solver. We can now access and plot whatever variables we like. For example, we could print the velocity at I::CartesianIndex using println(circ.flow.u[I]) or plot the whole pressure field using

using Plots
contour(circ.flow.p')

A set of flow metric functions have been implemented and the examples use these to make gifs such as the one above.

3D Taylor Green Vortex

The three-dimensional Taylor Green Vortex demonstrates many of the other available simulation options. First, you can simulate a non-trivial initial velocity field by passing in a vector function uλ(i,xyz) where i ∈ (1,2,3) indicates the velocity component uᵢ and xyz=[x,y,z] is the position vector.

function TGV(; pow=6, Re=1e5, T=Float64, mem=Array)
    # Define vortex size, velocity, viscosity
    L = 2^pow; U = 1; ν = U*L/Re
    # Taylor-Green-Vortex initial velocity field
    function (i,xyz)
        x,y,z = @. (xyz-1.5)*π/L               # scaled coordinates
        i==1 && return -U*sin(x)*cos(y)*cos(z) # u_x
        i==2 && return  U*cos(x)*sin(y)*cos(z) # u_y
        return 0.                              # u_z
    end
    # Initialize simulation
    return Simulation((L, L, L), (0, 0, 0), L; U, uλ, ν, T, mem)
end

This example also demonstrates the floating point type (T=Float64) and array memory type (mem=Array) options. For example, to run on an NVIDIA GPU we only need to import the CUDA.jl library and initialize the Simulation memory on that device.

import CUDA
@assert CUDA.functional()
vortex = TGV(T=Float32,mem=CUDA.CuArray)
sim_step!(vortex,1)

For an AMD GPU, use import AMDGPU and mem=AMDGPU.ROCArray. Note that Julia 1.9 is required for AMD GPUs.

Moving bodies

Flapping line segment flow

You can simulate moving bodies in WaterLily by passing a coordinate map to AutoBody in addition to the sdf.

using StaticArrays
function hover(L=2^5;Re=250,U=1,amp=π/4=0.5,thk=2ϵ+√2)
    # Line segment SDF
    function sdf(x,t)
        y = x .- SA[0,clamp(x[2],-L/2,L/2)]
        sum(abs2,y)-thk/2
    end
    # Oscillating motion and rotation
    function map(x,t)
        α = amp*cos(t*U/L); R = SA[cos(α) sin(α); -sin(α) cos(α)]
        R * (x - SA[3L-L*sin(t*U/L),4L])
    end
    Simulation((6L,6L),(0,0),L;U,ν=U*L/Re,body=AutoBody(sdf,map),ϵ)
end

In this example, the sdf function defines a line segment from -L/2 ≤ x[2] ≤ L/2 with a thickness thk. To make the line segment move, we define a coordinate transformation function map(x,t). In this example, the coordinate x is shifted by (3L,4L) at time t=0, which moves the center of the segment to this point. However, the horizontal shift varies harmonically in time, sweeping the segment left and right during the simulation. The example also rotates the segment using the rotation matrix R = [cos(α) sin(α); -sin(α) cos(α)] where the angle α is also varied harmonically. The combined result is a thin flapping line, similar to a cross-section of a hovering insect wing.

One important thing to note here is the use of StaticArrays to define the sdf and map. This speeds up the simulation since it eliminates allocations at every grid cell and time step.

Circle inside an oscillating flow

Oscillating flow

This example demonstrates a 2D oscillating periodic flow over a circle.

function circle(n,m;Re=250,U=1)
    # define a circle at the domain center
    radius = m/8
    body = AutoBody((x,t)->sum(abs2, x .- (n/2,m/2)) - radius)

    # define time-varying body force `g` and periodic direction `perdir`
    accelScale, timeScale = U^2/2radius, radius/U
    g(i,t) = i==1 ? -2accelScale*sin(t/timeScale) : 0
    Simulation((n,m), (U,0), radius; ν=U*radius/Re, body, g, perdir=(1,))
end

The g argument accepts a function with direction (i) and time (t) arguments. This allows you to create a spatially uniform body force with variations over time. In this example, the function adds a sinusoidal force in the "x" direction i=1, and nothing to the other directions.

The perdir argument is a tuple that specifies the directions to which periodic boundary conditions should be applied. Any number of directions may be defined as periodic, but in this example only the i=1 direction is used allowing the flow to accelerate freely in this direction.

Accelerating reference frame

WaterLily gives the possibility to set up a Simulation using time-varying boundary conditions for the velocity field, as demonstrated in this example. This can be used to simulate a flow in an accelerating reference frame. The following example demonstrates how to set up a Simulation with a time-varying velocity field.

using WaterLily
# define time-varying velocity boundary conditions
Ut(i,t::T;a0=0.5) where T = i==1 ? convert(T, a0*t) : zero(T)
# pass that to the function that creates the simulation
sim = Simulation((256,256), Ut, 32)

The Ut function is used to define the time-varying velocity field. In this example, the velocity in the "x" direction is set to a0*t where a0 is the acceleration of the reference frame. The Simulation function is then called with the Ut function as the second argument. The simulation will then run with the time-varying velocity field.

Periodic and convective boundary conditions

In addition to the standard free-slip (or reflective) boundary conditions, WaterLily also supports periodic boundary conditions, as demonstrated in this example. For instance, to set up a Simulation with periodic boundary conditions in the "y" direction the perdir=(2,) keyword argument should be passed

using WaterLily,StaticArrays

# sdf an map for a moving circle in y-direction
function sdf(x,t)
    norm2(SA[x[1]-192,mod(x[2]-384,384)-192])-32
end
function map(x,t)
    x.-SA[0.,t/2]
end

# make a body
body = AutoBody(sdf, map)

# y-periodic boundary conditions
Simulation((512,384), (1,0), 32; body, perdir=(2,))

Additionally, the flag exitBC=true can be passed to the Simulation function to enable convective boundary conditions. This will apply a 1D convective exit in the x direction (currently, only the x direction is supported for the convective outlet BC). The exitBC flag is set to false by default. In this case, the boundary condition is set to the corresponding value of the u_BC vector specified when constructing the Simulation.

using WaterLily

# make a body
body = AutoBody(sdf, map)

# y-periodic boundary conditions
Simulation((512,384), u_BC=(1,0), L=32; body, exitBC=true)

Writing to a VTK file

The following example demonstrates how to write simulation data to a .pvd file using the WriteVTK package and the WaterLily vtkwriter function. The simplest writer can be instantiated with

using WaterLily,WriteVTK

# make a sim
sim = make_sim(...)

# make a writer
writer = vtkwriter("simple_writer")

# write the data
write!(writer,sim)

# don't forget to close the file
close(writer)

This would write the velocity and pressure fields to a file named simmple_writer.pvd. The vtkwriter function can also take a dictionary of custom attributes to write to the file. For example, the following code can be run to write the body signed-distance function and λ₂ fields to the file

using WaterLily,WriteVTK

# make a writer with some attributes, need to output to CPU array to save file (|> Array)
velocity(a::Simulation) = a.flow.u |> Array;
pressure(a::Simulation) = a.flow.p |> Array;
_body(a::Simulation) = (measure_sdf!(a.flow.σ, a.body, WaterLily.time(a));
                                     a.flow.σ |> Array;)
lamda(a::Simulation) = (@inside a.flow.σ[I] = WaterLily.λ₂(I, a.flow.u);
                        a.flow.σ |> Array;)

# map field names to values in the file
custom_attrib = Dict(
    "Velocity" => velocity,
    "Pressure" => pressure,
    "Body" => _body,
    "Lambda" => lamda
)

# make the writer
writer = vtkWriter("advanced_writer"; attrib=custom_attrib)
...
close(writer)

The functions that are passed to the attrib (custom attributes) must follow the same structure as what is shown in this example, that is, given a Simulation, return an N-dimensional (scalar or vector) field. The vtkwriter function will automatically write the data to a .pvd file, which can be read by ParaView. The prototype for the vtkwriter function is:

# prototype vtk writer function
custom_vtk_function(a::Simulation) = ... |> Array

where ... should be replaced with the code that generates the field you want to write to the file. The piping to a (CPU) Array is necessary to ensure that the data is written to the CPU before being written to the file for GPU simulations.

Restarting from a VTK file

The restart of a simulation from a VTK file is demonstrated in this example. The ReadVTK package is used to read simulation data from a .pvd file. This .pvd must have been written with the vtkwriter function and must contain at least the velocity and pressure fields. The following example demonstrates how to restart a simulation from a .pvd file using the ReadVTK package and the WaterLily vtkreader function

using WaterLily,ReadVTK
sim = make_sim(...)
# restart the simulation
writer = restart_sim!(sim; fname="file_restart.pvd")

# append sim data to the file used for restart
write!(writer, sim)

# don't forget to close the file
close(writer)

Internally, this function reads the last file in the .pvd file and use that to set the velocity and pressure fields in the simulation. The sim_time is also set to the last value saved in the .pvd file. The function also returns a vtkwriter that will append the new data to the file used to restart the simulation. Note that the simulation object sim that will be filled must be identical to the one saved to the file for this restart to work, that is, the same size, same body, etc.

Multi-threading and GPU backends

WaterLily uses KernelAbstractions.jl to multi-thread on CPU and run on GPU backends. The implementation method and speed-up are documented in our preprint. In summary, a single macro WaterLily.@loop is used for nearly every loop in the code base, and this uses KernelAbstractactions to generate optimized code for each back-end. The speed-up with respect to a serial (single thread) execution is more pronounce for large simulations, and we have measure up to x8 speedups when multi-threading on an Intel Xeon Platinum 8460Y @ 2.3GHz backend, and up to 200x speedup on an NVIDIA Hopper H100 64GB HBM2 GPU. When maximizing the GPU load, a cost of 1.44 nano-seconds has been measured per degree of freedom and time step.

Note that multi-threading requires starting Julia with the --threads argument, see the multi-threading section of the manual. If you are running Julia with multiple threads, KernelAbstractions will detect this and multi-thread the loops automatically. As in the Taylor-Green-Vortex examples above, running on a GPU requires initializing the Simulation memory on the GPU, and care needs to be taken to move the data back to the CPU for visualization. See jelly fish for another non-trivial example.

Finally, KernelAbstractions does incur some CPU allocations for every loop, but other than this sim_step! is completely non-allocating. This is one reason why the speed-up improves as the size of the simulation increases.

Development goals

  • Immerse obstacles defined by 3D meshes using GeometryBasics.
  • Multi-CPU/GPU simulations.
  • Free-surface physics with Volume-of-Fluid or Level-Set.
  • External potential-flow domain boundary conditions.

If you have other suggestions or want to help, please raise an issue.

waterlily.jl's People

Contributors

weymouth avatar b-fg avatar marinlauber avatar navidcy avatar j-leetch avatar tzuyaohuang avatar blagneaux avatar giordano avatar asinghvi17 avatar j-massey avatar kimlaberinto avatar maeckha avatar michielstock avatar zitzeronion avatar t-bltg avatar

Stargazers

Haochen Li avatar Xuanzhao Gao avatar Iqbal Thaker avatar ben avatar AI for Science Talks avatar M. Shira avatar Kyle L. Davis avatar  avatar  avatar Sifan Wang avatar Alex Liberzon avatar RUIDONG LI avatar Kamila Zdybał avatar  avatar  avatar  avatar Felix Wechsler avatar Ilya Popov avatar XiaoyuXie avatar Ahmed ElGazzar avatar  avatar D.Raju avatar Esteban L. Castro avatar Violet avatar Thomas Bob avatar Guillaume Dalle avatar Qian Bao avatar  avatar Alberto Barradas avatar Grisha Szep avatar Ali Bouland avatar  avatar  avatar Zhen-Qi Liu avatar Utensil avatar Sokratis avatar Mathieu Bouchard avatar hamanishi avatar Piyush R. Maharana avatar Pete Bachant avatar Yi Zhou avatar Mark Griffiths avatar Fernando Oleo Blanco avatar  avatar  avatar Gabriel St-Pierre-Lemieux avatar JeffHugh avatar  avatar  avatar Taha Ozdemir avatar shinji avatar Akshay Patil avatar Dan Sandiford avatar  avatar W_Guan avatar Simon Beaumont avatar Suphawit Buaphan avatar Ludens avatar Wissotsky avatar Ghanem avatar Onri Jay Benally avatar  avatar Oliver Lylloff avatar  avatar wgzhao avatar Timoteo Dinelli avatar Will Hewson avatar Yi Dai avatar  avatar Vitor Heitor avatar Batın Buğday avatar Omar Al-Hassan avatar RomeoV avatar  avatar Jiacheng Wang avatar  avatar Weiwei Wang avatar Kyungdahm Yun avatar Daniele Pessina avatar  avatar Kai Partmann avatar Balty Pierre avatar LuoQY avatar  avatar Veronica Tamsitt avatar Jonas Krimmer avatar Tassawar Iqbal avatar  avatar  avatar Brahmanda Sudarsana avatar Harry BM avatar  avatar  avatar Guilherme A. L. da Silva avatar Philip Cardiff avatar  avatar  avatar Miguel Fosas de Pando avatar  avatar AperGra avatar

Watchers

jm avatar Lucian avatar Hideaki Igarashi avatar  avatar Guilherme A. L. da Silva avatar James Cloos avatar  avatar  avatar Alberto Fernandez avatar  avatar  avatar Mohamed Tarek avatar Ali Ramadhan avatar River_Flow avatar Pedro Costa avatar  avatar ZIQIANG YANG avatar  avatar  avatar  avatar

waterlily.jl's Issues

Saving each time step flow field values

I don't know much about Julia. What I want to have from the simulation of a flow over a cylinder is extracting the full field of velocity and vorticity for a specific time duration. In other words, I want to save some arrays like [T,n,m], T as time steps, M and N as spatial domains. Could you please help me how can I do that?

using WaterLily
using LinearAlgebra: norm2
Re=250
n=3*2^7
m=2^8
radius=m/6
center, ν = m/2, radius/Re
body = AutoBody((x,t)->norm2(x .- center) - radius)
sim=Simulation((n+2,m+2), [1.,0.], radius; ν, body)
sim_step!(sim,10)
using MATLAB
write_matfile("Desktop\test_u.mat";sim.flow.u)
vrtx=sim.flow.σ
write_matfile("Desktop\test_vrtx.mat";vrtx)

Also, is "sim.flow.σ" vorticity of flow?

Any help would be appreciated.

Thank you!

Fluid Structure Interaction

I've been experimenting in WaterLily with heaving airfoils recently and I'm interested in implementing leading-edge flexibility via a torsional spring model. Is fluid structure interaction feasible with WaterLily? Has any work been done on coupling body displacement/dynamics with fluid forcing? I'm newer to Julia and WaterLily any advice would be appreciated.

Thanks for this great package!

Simulating two different fluids

Hi, I found this repo and love the library. I think that adding moving objects this easily is great. I have been using WaterLily for some time.

My new project involves a smoke simulation with a moving object. Therefore I was wondering, can WaterLily simulate 2 different fluids? especially because densities become variable on the domain and buoyancy forces become important.

The details of the simulation: Imagine a pipe full of smoke, then a piston accelerates the smoke out of the pipe into the air forming a smoke ring. Like in the donut example. I want to study how the ring changes with the simulation parameters. Smoke is much denser than air, that's my biggest problem.

Another approach I thought of was to add a lot of very small particles and update their position using the get force function and then add the buoyancy force but I don't know how good this approach is.

What do you recommend?

I could of course just study the vorticity of the air and don't mess with two different fluids, but I was wondering if WaterLily could do the job.

Thanks.

Courant number (CFL)

Hi,
I have an issue with having too much noise for the force signal acting on a body. I want to know how can I change/optimize the CFL number here [https://github.com/weymouth/WaterLily.jl/blob/master/src/Flow.jl#L165] ? ? what are these 0.5 showing in these lines:

    BDIM!(a); scale_u!(a,0.5); BC!(a.u,U,a.exitBC,a.perdir)
    project!(a,b,0.5); BC!(a.u,U,a.exitBC,a.perdir)

Error in test and in example

ThreeD_TaylorGreenVortex.jl

ulia> TGV_video()
1%, tU/L=0.0039, Δt=0.25
1%, tU/L=0.0117, Δt=0.5
1%, tU/L=0.0195, Δt=0.5
1%, tU/L=0.0273, Δt=0.5
1%, tU/L=0.0351, Δt=0.5
1%, tU/L=0.043, Δt=0.5
1%, tU/L=0.0508, Δt=0.5
1%, tU/L=0.0586, Δt=0.5
1%, tU/L=0.0664, Δt=0.5
1%, tU/L=0.0742, Δt=0.501
1%, tU/L=0.0821, Δt=0.501
1%, tU/L=0.0899, Δt=0.501
1%, tU/L=0.0977, Δt=0.501
ERROR: ModernGL.ContextNotAvailable("glTexSubImage3D, not available for your driver, or no valid OpenGL context available")
Stacktrace:
 [1] getprocaddress_e at C:\Users\vsarn\.julia\packages\ModernGL\rVuW2\src\ModernGL.jl:45 [inlined]
 [2] glTexSubImage3D(::UInt32, ::Int64, ::Int64, ::Int64, ::Int64, ::Int64, ::Int64, ::Int64, ::UInt32, ::UInt32, ::Array{Float32,3}) at C:\Users\vsarn\.julia\packages\ModernGL\rVuW2\src\functionloading.jl:66
 [3] texsubimage at C:\Users\vsarn\.julia\packages\GLMakie\Y3YHT\src\GLAbstraction\GLTexture.jl:374 [inlined] (repeats 2 times)
 [4] gpu_setindex!(::GLMakie.GLAbstraction.Texture{Float32,3}, ::Array{Float32,3}, ::UnitRange{Int64}, ::UnitRange{Int64}, ::Vararg{UnitRange{Int64},N} where N) at C:\Users\vsarn\.julia\packages\GLMakie\Y3YHT\src\GLAbstraction\GLTexture.jl:275
 [5] setindex!(::GLMakie.GLAbstraction.Texture{Float32,3}, ::Array{Float32,3}, ::UnitRange{Int64}, ::UnitRange{Int64}, ::UnitRange{Int64}) at C:\Users\vsarn\.julia\packages\GLMakie\Y3YHT\src\GLAbstraction\AbstractGPUArray.jl:60
 [6] update!(::GLMakie.GLAbstraction.Texture{Float32,3}, ::Array{Float32,3}) at C:\Users\vsarn\.julia\packages\GLMakie\Y3YHT\src\GLAbstraction\AbstractGPUArray.jl:80
 [7] (::GLMakie.GLAbstraction.var"#10#11"{GLMakie.GLAbstraction.Texture{Float32,3}})(::Array{Float32,3}) at C:\Users\vsarn\.julia\packages\GLMakie\Y3YHT\src\GLAbstraction\AbstractGPUArray.jl:211
 [8] #invokelatest#1 at .\essentials.jl:712 [inlined]
 [9] invokelatest at .\essentials.jl:711 [inlined]
 [10] setindex!(::Observable{Array{Float32,3}}, ::Array{Float32,3}; notify::Observables.var"#6#8") at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:132
 [11] setindex! at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:126 [inlined]
 [12] MapUpdater at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:241 [inlined]
 [13] (::Observables.OnUpdate{Observables.MapUpdater{AbstractPlotting.var"#183#185"{Int64},Array{Float32,3}},Tuple{Observable{Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}}}})(::Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}) at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:218
 [14] setindex!(::Observable{Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}}, ::Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}; notify::Observables.var"#6#8") at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:130
 [15] setindex!(::Observable{Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}}, ::Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}) at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:126
 [16] (::AbstractPlotting.var"#201#203"{DataType,Observable{Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}}})(::Tuple{}, ::Tuple{Array{Float64,3}}) at C:\Users\vsarn\.julia\packages\AbstractPlotting\tHSnG\src\interfaces.jl:615
 [17] (::Observables.OnUpdate{AbstractPlotting.var"#201#203"{DataType,Observable{Tuple{IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},IntervalSets.Interval{:closed,:closed,Float32},Array{Float32,3}}}},Tuple{Observable{Tuple{}},Observable{Tuple{Array{Float64,3}}}}})(::Tuple{Array{Float64,3}}) at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:218
 [18] setindex!(::Observable{Tuple{Array{Float64,3}}}, ::Tuple{Array{Float64,3}}; notify::Observables.var"#6#8") at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:130
 [19] setindex! at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:126 [inlined]
 [20] MapUpdater at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:241 [inlined]
 [21] (::Observables.OnUpdate{Observables.MapUpdater{typeof(tuple),Tuple{Array{Float64,3}}},Tuple{Observable{Array{Float64,3}}}})(::Array{Float64,3}) at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:218
 [22] setindex!(::Observable{Array{Float64,3}}, ::Array{Float64,3}; notify::Observables.var"#6#8") at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:130
 [23] setindex!(::Observable{Array{Float64,3}}, ::Array{Float64,3}) at C:\Users\vsarn\.julia\packages\Observables\0wrF6\src\Observables.jl:126
 [24] setindex!(::Volume{...}, ::Array{Float64,3}, ::Int64) at C:\Users\vsarn\.julia\packages\AbstractPlotting\tHSnG\src\dictlike.jl:190
 [25] (::var"#11#15"{Int64,Int64,Flow{4,3},MultiLevelPoisson{4,3},Volume{...},Float64,Int64})(::Int64) at C:\Users\vsarn\Documents\FrozenCat\Julia\Test\water.jl:42
 [26] record(::var"#11#15"{Int64,Int64,Flow{4,3},MultiLevelPoisson{4,3},Volume{...},Float64,Int64}, ::Scene, ::String, ::UnitRange{Int64}; framerate::Int64, compression::Int64, sleep::Bool) at C:\Users\vsarn\.julia\packages\AbstractPlotting\tHSnG\src\display.jl:537
 [27] TGV_video(::Int64, ::Float64) at C:\Users\vsarn\Documents\FrozenCat\Julia\Test\water.jl:30
 [28] TGV_video() at C:\Users\vsarn\Documents\FrozenCat\Julia\Test\water.jl:7
 [29] top-level scope at none:0

Test:

julia> Pkg.test("WaterLily")
    Testing WaterLily
Status `C:\Users\vsarn\AppData\Local\Temp\jl_ycgAph\Manifest.toml`
  [ed894a53] WaterLily v0.1.0 #master (https://github.com/weymouth/WaterLily.git)
  [2a0f44e3] Base64
  [8ba89e20] Distributed
  [b77e0a4c] InteractiveUtils
  [8f399da3] Libdl
  [37e2e46d] LinearAlgebra
  [56ddb016] Logging
  [d6f4376e] Markdown
  [9a3f8284] Random
  [9e88b42a] Serialization
  [6462fe0b] Sockets
  [8dfed614] Test
Test Summary: | Pass  Total
util.jl       |    2      2
Test Summary: | Pass  Total
Poisson.jl    |    2      2
Test Summary:        | Pass  Total
MultiLevelPoisson.jl |    2      2
Test Summary: | Pass  Total
Body.jl       |    5      5
Flow.jl: Test Failed at C:\Users\vsarn\.julia\packages\WaterLily\4wkpL\test\runtests.jl:66
  Expression: L₂(a.u[:, :, 2] .- U[2]) < 1.0e-6
   Evaluated: 1.4910472215055462e-6 < 1.0e-6
Stacktrace:
 [1] top-level scope at C:\Users\vsarn\.julia\packages\WaterLily\4wkpL\test\runtests.jl:66
 [2] top-level scope at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Test\src\Test.jl:1113
 [3] top-level scope at C:\Users\vsarn\.julia\packages\WaterLily\4wkpL\test\runtests.jl:59
Test Summary: | Pass  Fail  Total
Flow.jl       |    1     1      2
ERROR: LoadError: Some tests did not pass: 1 passed, 1 failed, 0 errored, 0 broken.
in expression starting at C:\Users\vsarn\.julia\packages\WaterLily\4wkpL\test\runtests.jl:57
ERROR: Package WaterLily errored during testing
Stacktrace:
 [1] pkgerror(::String, ::Vararg{String,N} where N) at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\Types.jl:53
 [2] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; coverage::Bool, julia_args::Cmd, test_args::Cmd, test_fn::Nothing) at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\Operations.jl:1503
 [3] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; coverage::Bool, test_fn::Nothing, julia_args::Cmd, test_args::Cmd, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:316
 [4] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:303
 [5] #test#68 at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:297 [inlined]
 [6] test at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:297 [inlined]
 [7] #test#67 at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:296 [inlined]
 [8] test at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:296 [inlined]
 [9] test(::String; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:295
 [10] test(::String) at D:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.4\Pkg\src\API.jl:295
 [11] top-level scope at none:0

julia> 

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Change default time in force integration

In the pressure force integration the default is to use t=0 for the body position, but this doesn't correspond to the pressure, which is stored at time(sim).

I think the default behavior should be to measure the body at time(sim), where the pressure is also defined. This would require passing sim or flow directly to the function.

Docs are pointing to wrong/old repo URL

The docs are pointing to an old repo URL.
This can be seen when trying to click on "source code" for any of the methods linked in the docs, and it'll point to a 404 page.
This can also be seen when trying to click on "Edit on Github" link at the top of the page of the docs

Heaving Cylinder in still water

Hello,
Thanks a lot for the work done for this package, it is very intuitive and easy to compute three-dimensional flows on my laptop.

For my work, I need to assess the inertial forces on a heaving cylinder in still water for a large number of conditions (hence my interest in fast simulations!). I could easily realise simulations of a moving cylinder with a constant inflow velocity.
However, I encountered two main problems when attempting to do it in still water:

    1. I get confused with the non-dimensional time as it seems to rely on the inlet fluid velocity. When the inflow velocity is set to 0, the simulation gets stuck at the first time-step. How are we meant to progress the simulation forward in time with no inflow velocity?
    1. Additionally, is it possible to set all the domain boundaries as outlets?

Many thanks for your help!

examples/TwoD_block.jl fails

examples/TwoD_block.jl does not run for me. Here is what I do

$ julia -e 'using Pkg; Pkg.add("Plots")'
$ JULIA_LOAD_PATH=src: julia -e 'include("examples/TwoD_block.jl"); TwoD_block_video(6)'
(L, ν) = (8.0, 0.032)
ERROR: MethodError: no method matching CartesianIndex{2}(::CartesianIndex{2})
Closest candidates are:
  CartesianIndex{2}() where N at multidimensional.jl:73
  CartesianIndex{2}(!Matched::Integer...) where N at multidimensional.jl:69
  CartesianIndex{2}(!Matched::Tuple{Vararg{Integer,N}}) where N at multidimensional.jl:64
  ...
Stacktrace:
 [1] oneunit(::CartesianIndex{2}) at ./number.jl:320
 [2] near(::CartesianIndex{2}, ::Int64) at /home/lisergey/src/WaterLily/src/GMG.jl:1
 [3] restrictPS(::Array{Float64,3}) at /home/lisergey/src/WaterLily/src/GMG.jl:7
 [4] MultiLevelPS(::Array{Float64,3}) at /home/lisergey/src/WaterLily/src/GMG.jl:29
 [5] TwoD_block_video(::Int64, ::Int64) at /home/lisergey/src/WaterLily/examples/TwoD_block.jl:18
 [6] TwoD_block_video(::Int64) at /home/lisergey/src/WaterLily/examples/TwoD_block.jl:5
 [7] top-level scope at none:0
$ julia --version
julia version 1.0.3
$ lsb_release -da
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 10 (buster)
Release:	10
Codename:	buster

Solving a passive scalar conservation equation

Hoping to add functionality to an (initially) passive scalar calculation of N scalars to the WaterLily flow solver. I'd label this as a want but not clear how to do it through the website.

Taylor Vortex file

Sorry, I am a beginner with this. I downloaded everything including CUDA, but when I attempt to run this Taylor Vortex thing I get this error message. What am I doing wrong.

ERROR: AssertionError: CUDA.functional()
Stacktrace:
[1] top-level scope
@ ~/Documents/Julia-March-24/Tiina-WL-2.jl:18

Repeated AutoBody additions cause StackOverflow

Respected sir,

I am facing an issue while using this code. Actually, when I am using more than a certain number (6) of members as "sdf" in a code, it gives me "StackOverflowError". Can you please help me figure out any possible solution for it?

Thank you for your time and consideration.

Yours sincerely,
Kunal Ghosh

examples/TwoD_circle.jl failt with julia 1.4.1

example/TwoD_circle.jl fails with julia 1.4.1:

/home/lisergey/WaterLily.jl
$ JULIA_LOAD_PATH=.: julia examples/TwoD_circle.jl
ERROR: LoadError: LoadError: syntax: invalid keyword argument syntax "ϵ"
Stacktrace:
 [1] top-level scope at /home/lisergey/WaterLily.jl/src/Body.jl:27
 [2] include(::Module, ::String) at ./Base.jl:377
 [3] include(::String) at /home/lisergey/WaterLily.jl/src/WaterLily.jl:1
 [4] top-level scope at /home/lisergey/WaterLily.jl/src/WaterLily.jl:15
 [5] include(::Module, ::String) at ./Base.jl:377
 [6] top-level scope at none:2
 [7] eval at ./boot.jl:331 [inlined]
 [8] eval(::Expr) at ./client.jl:449
 [9] top-level scope at ./none:3
in expression starting at /home/lisergey/WaterLily.jl/src/Body.jl:27
in expression starting at /home/lisergey/WaterLily.jl/src/WaterLily.jl:15
ERROR: LoadError: Failed to precompile WaterLily [ed894a53-35f9-47f1-b17f-85db9237eebd] to /home/lisergey/.julia/compiled/v1.4/WaterLily/9Odjf_w5bnh.ji.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] compilecache(::Base.PkgId, ::String) at ./loading.jl:1272
 [3] _require(::Base.PkgId) at ./loading.jl:1029
 [4] require(::Base.PkgId) at ./loading.jl:927
 [5] require(::Module, ::Symbol) at ./loading.jl:922
 [6] include(::Module, ::String) at ./Base.jl:377
 [7] exec_options(::Base.JLOptions) at ./client.jl:288
 [8] _start() at ./client.jl:484
in expression starting at /home/lisergey/WaterLily.jl/examples/TwoD_circle.jl:1

The example works with 1.6.0, but 1.4.1 is a version from current Ubuntu.

$ julia --version
julia version 1.4.1
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.2 LTS"

3D simulations for several shape: flow u is NaN or 0

I discovered this simulator and I am really appreciating it.

I have been playing with it and I found out that in several case, when dealing with 3D shape, like a cylinder, the sim.flow.u array is composed by only 0 and NaN.

Test it trying the donut example changing its sdf.

body = AutoBody() do xyz, t
    x, y, z = xyz - center
    # donut
    # norm2(SA[x, norm2(SA[y, z])-R]) - r 
    
    # cylinder
    d = SA[norm2(SA[y, z])-R, abs(x)-r]
    min(maximum(d), 0.0) + norm2(max.(d, 0.0))
end

Now, maybe I misunderstood something or it is just an unlucky choice of parameters, or it is a problem.

Edit:
The same problem arises with a 2D box.

Pluto_test.jl

Line 18: I needed to import 'Plots' as well as 'WaterLily, PlutoUI'. Would it be better to create a pull request on my fork?

Diverging pressure for rotational motion

Hi Gabriel,

I know you are busy working on another project, but I've been struggling with this issue for the past few weeks and Im starting to run out of ideas on what could solve this.

I've come across this issue while working on this kind of simulation:
jl_ex8Pu3iRYr

Everything works fine when I try to display the vorticity, but when it comes to the pressure, the mean skyrockets:
image
Substracting it at every new iteration makes the pressure seem like there was no issue in the first place, but I'm sure there could be a way to not use this trick but the raw pressure instead.

I tried finding what was causing this issue and I thus used your example of TwoD_hover.jl which is also using a rotational motion. I indeed find similar results with a pressure that is not physically consistent. I then tried to figure out if it was only due to motions cumputed with rotation matrix, but it seems that sin(omega*t) displacement also cause this issue. Same for sin(kx-omega*t) if k is too small. Here you can see a motion of this kind with k=1.5 which is around the smallest value I can make it work with:
jl_BGBwPAPsr0

I also tried to play on the amplitude of the rotation and it indeed have an impact. When the rotation is really small the pressure gets back to normal, but what is weird is that the smallest amplitude working is way less than the amplitude of the tail in the last figure.

Since I've seen that the foil you are using in your new project is having a rotational motion, I was therefore wondering if you had ever encoutered this issue before or if you had any idea on what could be the reason behind this phenomenon.

I'll keep on trying to find a solution on my own if you don't have time to look at this in depth.

Thanks,
Bastien

circle position

Hello again,

I am wondering how to define the position of the circle as:

x= a y=b, where a and be are not the same

n=32^7
m=2
2^7
radius=m/6
center, ν = m/2, radius/Re
body = AutoBody((x,t)->norm2(x .- center) - radius)
sim=Simulation((n+2,m+2), [1.,0.], radius; ν, body)

with this code, x=y=m/6

Thank you

Confusion with the Circle Tutorial and Unicode Characters

Hello,
first up thanks for this package, it looks really interesting.
When I was following the first code example, I stumbled upon some weird errors in Julia even though my code seemed literally the same. Upon using a code diff checker I noted that the "v" used in the example is not the latin "v" (U+0076) but the greek nu (U+03BD), which tripped up my code which was half typed by me and half copied from the example.
I guess the usage of the greek nu is intended to denote the correct formula symbol, but I would suggest to add a clarification in the tutorial to prevent confusion with these letters without optical differences.
Cheers, Carlo

Requires > 1.4

The code looks interesting! Just a commen treally - does it really require >1.5 version?
Current stable is 1.4.2 and I have that installed.

LoadError: MethodError: no method matching record in ThreeD_donut although another package (ThreeD_TaylorGreenVortex) with “record” works

First package

using WaterLily
using LinearAlgebra: norm2
using Makie

function donut_sim(p=6,Re=1e3)
    # Define simulation size, velocity, viscosity
    n,U = 2^p, [1, 0, 0]
    center,R,r = [n/2,n/2,n/2], n/4, n/16
    ν = norm2(U)*R/Re

    # Apply signed distance function for a torus
    c = BDIM_coef(2n+2,n+2,n+2,3) do xyz  #
        x,y,z = xyz - center
        norm2([x,norm2([y,z])-R])-r
    end

    # Initialize Flow, Poisson and make struct
    u = zeros(2n+2,n+2,n+2,3)
    a = Flow(u,c,U,ν=ν)
    b = MultiLevelPoisson(c)
    Simulation(norm2(U),R,a,b),center
end

function flowdata(sim)
    @inside sim.flow.σ[I] = WaterLily.ω_θ(I,[1,0,0],center,sim.flow.u)*sim.L/sim.U
    @view sim.flow.σ[2:end-1,2:end-1,2:end-1]
end
function geomdata(sim)
    @inside sim.flow.σ[I] = sum(sim.flow.μ₀[I,i]+sim.flow.μ₀[I+δ(i,I),i] for i=1:3)
    @view sim.flow.σ[2:end-1,2:end-1,2:end-1]
end

function make_video!(sim::Simulation;name="file.mp4",verbose=true,t₀=0.0,Δprint=0.1,nprint=24*3)
    # plot the geometry and flow
    scene = contour(geomdata(sim),levels=[0.5])
    scene = contour!(scene,flowdata(sim),levels=[-7,7],
                     colormap=:balance,alpha=0.2,colorrange=[-7,7])
    scene_data = scene[end]

    # Plot flow evolution
    tprint = t₀+WaterLily.sim_time(sim)
    record(scene,name,1:nprint,compression=5) do i
        tprint+=Δprint
        sim_step!(sim,tprint;verbose)
        println("video ",round(Int,i*100/nprint),"% complete")
        scene_data[1] = flowdata(sim)
    end
    return scene
end

donut,center = donut_sim();
scene = make_video!(donut);

Second Package:

using WaterLily
using LinearAlgebra: norm2
using Makie

function flowdata(sim)
    @inside sim.flow.σ[I] = WaterLily.ω_mag(I,sim.flow.u)*sim.L/sim.U
    return @view sim.flow.σ[2:end-1,2:end-1,2:end-1]
end
function TGV_video(p=6,Re=1e5,Δprint=0.1,nprint=100)
    # Define vortex size, velocity, viscosity
    L = 2^p; U = 1; ν = U*L/Re

    # Taylor-Green-Vortex initial velocity field
    u = apply(L+2,L+2,L+2,3) do i,vx
        x,y,z = @. (vx-1.5)*π/L                # scaled coordinates
        i==1 && return -U*sin(x)*cos(y)*cos(z) # u_x
        i==2 && return  U*cos(x)*sin(y)*cos(z) # u_y
        return 0.                              # u_z
    end

    # Initialize simulation
    c = ones(L+2,L+2,L+2,3)  # no immersed solids
    a = Flow(u,c,zeros(3),ν=ν)
    b = MultiLevelPoisson(c)
    sim = Simulation(U,L,a,b)

    # plot the vorticity modulus
    scene = Scene(backgroundcolor = :black)
    scene = volume!(scene,flowdata(sim),colorrange=(π,4π),algorithm = :absorption)
    vol_plot = scene[end]

    # Plot flow evolution
    tprint = 0.0
    record(scene,"file.mp4",1:nprint,framerate=24,compression=5) do i
        tprint += Δprint
        sim_step!(sim,tprint)
        println("video ",round(Int,i/nprint*100),"% complete")
        vol_plot[1] = flowdata(sim)
    end
    return sim,scene
end

The first package throws the error concerning the record method, the second package does fine and has the record method as well.

ERROR: LoadError: MethodError: no method matching record(::var"#134#135"{Bool,Float64,Int64,Simulation,Contour{...}}, ::Scene, ::String, ::UnitRange{Int64}; compression=5)
Closest candidates are:
  record(::Any, ::Any, ::Any, ::Any; framerate) at /home/meck/.julia/packages/AbstractPlotting/rWoon/src/display.jl:463 got unsupported keyword argument "compression"
  record(::Any, ::Any, ::Any; framerate) at /home/meck/.julia/packages/AbstractPlotting/rWoon/src/display.jl:443 got unsupported keyword argument "compression"
Stacktrace:
 [1] kwerr(::NamedTuple{(:compression,),Tuple{Int64}}, ::Function, ::Function, ::Scene, ::String, ::UnitRange{Int64}) at ./error.jl:157
 [2] make_video!(::Simulation; name::String, verbose::Bool, t₀::Float64, Δprint::Float64, nprint::Int64) at /home/meck/Documents/Bachelor Arbeit/WaterLily/examples/ThreeD_donut.jl:42
 [3] make_video!(::Simulation) at /home/meck/Documents/Bachelor Arbeit/WaterLily/examples/ThreeD_donut.jl:35
 [4] top-level scope at /home/meck/Documents/Bachelor Arbeit/WaterLily/examples/ThreeD_donut.jl:52
in expression starting at /home/meck/Documents/Bachelor Arbeit/WaterLily/examples/ThreeD_donut.jl:52

I am confused because those functions reside in the same folder.

MultiLevelPoisson solver tolerance not independent of domain size

I just realized (while looking at pressure forces) that the pressure field oscillates when the domain is large (384,256), see attached script.

I have managed to track the issue to the MultiLevelPoisson/solver! function.
This is linked to the V-cycle criterion that checks if the residual on the upper level (r₂) is below a certain tolerance (tol) before performing the Vcycle

while r₂>tol && nᵖ<itmx
      Vcycle!(ml)
      smooth!(p); r₂ = L₂(p)
      log && push!(res,r₂)
      nᵖ+=1
end

The issue is that (r₂) depends on the grid size through p.r as L₂(p::Poisson) = p.r ⋅ p.r, while tol does not. This means that in some cases, the solver! doesn't perform any V-cycle, which doesn't change the pressure field.

The proposed solution is to scale the tol by the grid size, compute it once and store in in the MultiLevelPoisson structure, see #97
TwoD_circle_pressure_check.zip

Plotting gif of examples seems broken

Hello again,

when trying to plot the 2D examples (such as the TwoD_circle) with the functions provided in TwoD_plots.jl,
the sim_gif! function returns the following error:

MethodError: no method matching _as_gradient(::Array{RGBA{Float64},1}) Closest candidates are: _as_gradient(!Matched::ColorGradient) at C:\Users\Carlo\.julia\packages\Plots\8GUYs\src\utils.jl:136 _as_gradient(!Matched::Colorant) at C:\Users\Carlo\.julia\packages\Plots\8GUYs\src\utils.jl:137 in top-level scope at lily_plots.jl:58 in at lily_plots.jl:28 in #sim_gif!#58 at lily_plots.jl:29 in macro expansion at base\timing.jl:174 in macro expansion at Plots\8GUYs\src\animation.jl:181 in macro expansion at lily_plots.jl:34 in macro expansion at Plots\8GUYs\src\animation.jl:170 in frame at Plots\8GUYs\src\animation.jl:18 in frame at Plots\8GUYs\src\animation.jl:20 in png at Plots\8GUYs\src\output.jl:7 in show at Plots\8GUYs\src\output.jl:215 in _show at Plots\8GUYs\src\backends\gr.jl:1955 in gr_display at Plots\8GUYs\src\backends\gr.jl:723 in gr_display at Plots\8GUYs\src\backends\gr.jl:1550 in gr_set_gradient at Plots\8GUYs\src\backends\gr.jl:661 in gr_set_gradient at Plots\8GUYs\src\backends\gr.jl:650

I have played around with the whole thing a bit, and found that using the mu-body_plot! function also returns the same error.
Is this maybe due to some updated dependencies, since it seems to originate somewhere in the GR backend?
I am using [email protected] with [email protected]

Thanks for your help in advance!

README.md needs update? MethodError: no method matching sim_step!(::Simulation; t_end::Int64)

An attempt to run the exact code from the example 'Flow over a circle' in README.md produces the following error:

julia> sim_step!(circ,t_end=10)
ERROR: MethodError: no method matching sim_step!(::Simulation; t_end::Int64)

Closest candidates are:
  sim_step!(::Simulation, ::Any; verbose, remeasure) got unsupported keyword argument "t_end"
   @ WaterLily ~/.julia/packages/WaterLily/rv3IZ/src/WaterLily.jl:83

It seems README.md needs updating

Particle flows visualization

I have adapted the TwoBody_circle example to use a different shape. The flow seems to penetrate into the body.

Is this a problem?

image

Test fail on MacOS in testing enviroment

I can replicate the error mentioned in #98 and #95 on MacOS, if I run the test suite using
] test
but not if I run the tests from the REPL. I guess that means there is something in a normal build which is not being replicated in the testing environment.

In the test environment, I've tracked this down to a NaN cropping up in the Vcycle.

Circle example not working

Default circle function does not work:

function circle(n,m;Re=250,U=1)
    radius, center = m/8, m/2
    body = AutoBody((x,t)->sum(abs2, x .- center) - radius)
    Simulation((n,m), (U,0), radius; ν=U*radius/Re, body)
end

circ = circle(3*2^6,2^7)
t_end = 10
sim_step!(circ,t_end)

Yields the following error:

MethodError: no method matching WaterLily.Simulation(::Tuple{Int64, Int64}, ::Tuple{Int64, Int64}, ::Float64; ν=0.064, body=WaterLily.AutoBody{WaterLily.var"#comp#45"{Main.var"workspace#37".var"#2#3"{Float64, Float64}, WaterLily.var"#43#44"}, WaterLily.var"#43#44"}(WaterLily.var"#comp#45"{Main.var"workspace#37".var"#2#3"{Float64, Float64}, WaterLily.var"#43#44"}(Main.var"workspace#37".var"#2#3"{Float64, Float64}(64.0, 16.0), WaterLily.var"#43#44"()), WaterLily.var"#43#44"()))

Closest candidates are:

WaterLily.Simulation(::Tuple, !Matched::Vector, ::Number; Δt, ν, U, ϵ, uλ, body, T) at ~/.julia/packages/WaterLily/1t0mb/src/WaterLily.jl:60

var"#circle#1"(::Int64, ::Int64, ::typeof(Main.var"workspace#37".circle), ::Int64, ::Int64)@[Other: 4](http://localhost:1234/edit?id=0cb9c7e4-e058-11ee-2ff4-b5a8d4e4ecb5#)
circle(::Int64, ::Int64)@[Other: 1](http://localhost:1234/edit?id=0cb9c7e4-e058-11ee-2ff4-b5a8d4e4ecb5#)
top-level scope@[Local: 2](http://localhost:1234/edit?id=0cb9c7e4-e058-11ee-2ff4-b5a8d4e4ecb5#)[inlined]

How to use separate map functions for different bodies?

Respected sir,

I am facing an issue while using this code. Actually, I want to use separate map functions for each of the separate bodies. Could you please help me with how to do it or refer me to any other resources for the same?

Thank you for your time and consideration.

Yours sincerely,
Kunal Ghosh

Modeling flow over a full 3D normal facing disk

Following along with your YouTube example for various 3D shapes (https://youtu.be/OO0Nz3uWDc8?si=FGseNtBGkDdYK05p), when changing the "map" function for the corner disk to place it in the center of the domain and change it to a full disk, the model does not seem to converge. I have tried increasing the resolution, the size of the domain, the size and thickness of the disk, and it's position relative to the inlet and outlet and the same issues persist. More specifically, the errors given show that the lambda2 data is a non-hermitian matrix.

original working corner disk

function make_sim(;n=2^6, U=1, Re=1000, mem=Array)
    D = n/2
    R = D/2

    norm2(x) = sum(abs2,x)
    function sdf(xyz,t)
        x,y,z = xyz
        r = norm2(SA[y,z]);       
        norm2(SA[x,r-min(r,R)]) - 1.5  
    end
    map(xyz,t) = xyz - SA[2n/3,0,0]

    return Simulation((2n,n,n), (U,0,0), D; 
        ν=U*D/Re, body=AutoBody(sdf,map), mem)
end

change to full centralized disk

map(xyz,t) = xyz - SA[2n/3,n/2,n/2]

Similarly, the same issues show to persist if the corner disk is moved to

map(xyz,t) = xyz - SA[n/2,0,0]

Integration with Flux

Thank you for this nice package!

I am trying to use Flux.jl together with a simulation, but Zygote complains with
Mutating arrays is not supported -- called copyto!(Array{Float32, 3}, ...)

Thank you for your help

A toy example is:

using WaterLily, Flux

sim = Simulation((32, 32), (0, 0), 5; U=1, ν=1e-4, T=Float32)
model = Conv((3,3), 2 => 2; pad=SamePad() )

function comploss(model, sim) 
    t₀ = round(sim_time(sim))
    loss = 0.0
    for tᵢ in range(t₀, t₀+1; step=0.1)
        sim_step!(sim,tᵢ; remeasure=false)
        sim.flow.u .+= model(sim.flow.u[:,:,:,:])[:,:,:]
        loss += sum(sim.flow.u.^2)
    end
    return loss
end

grad = gradient(m -> comploss(m, sim), model)

Prescribing boundary conditions

I've been playing around with the package a bit for 2D flows and know I can prescribe constant valued Dirichlet boundary conditions on the x and y boundaries using an array of length 2 (for example [1.0,0.0]) as an argument to the Simulation constructor. Is there a way to assign non constant boundary conditions and Neumann conditions? In particular I am interested in prescribing periodic conditions.

Vtk writer, writing always to vtk_data folder

Currently the vtk writer always writes to the vtk_data folder. In case multiple simulations are done after each other, this will fill up the same folder with all the results. In the filename you can still specify which case it is, but it would be cleaner to have the option to specificy the folder where you would like the vti & pvd files to be stored

https://github.com/weymouth/WaterLily.jl/blob/888458479f62eb17282aedf822a46234b942a6d0/src/vtkWriter.jl#L16C66-L16C80

Add support for objects specified by NURBS?

I've worked through the dogfish example, which produces an sdf from a B-spline and is very nice. However, it's my understanding that the sdf generating function doesn't measure closest distance but are just conformal maps in the y-direction? Will this cause some numerical error when calculating the gradients? The projection (and closest distance) of any point onto the curve should be orthogonal to the curve.

If the order and knot vector of the NURBS or B-spline is such that second derivatives exist, then equation 6.3 in the NURBS Book 2nd ed by Piegl and Tiller allows calculation of the sdf. The downside is that you need to check every span. Additionally, if the curve is not closed or C2 continuous, it will make everything so much more complicated. That said, the continuity can be simply read off the knot vector. Additionally, it makes physical sense that the head and tail of the dogfish are C0 continuous.

Anyway, I think this more of a topic for discussion rather than an action item.

I've searched for Python, Julia and C++ tools to calculate the distance of a point from a NURBS curve and haven't found anything on github yet. Of the NURBS packages in Julia, the BasicBSpline package is in active development and is pretty well organized. It's just one person though and while off to a good start, there's still a lot left to do. Python-NURBS is really good for curve fitting.

Adjusting for Surface Mesh

Hi, cool package and very nice work on WaterLily.jl!

In the JuliaCon 2021 talk it is mentioned that importing a surface mesh is a fairly easy adjustment. However, I am having a go at it and could use more insight. My goal is to perform an apropos Santa's Sleigh CFD visualization. Figured out how to import the geometry, but the next step is confusing me.

Can AutoBody be used in conjunction with the surface mesh? If so then how does sdf and map come into play? Or should I consider making a new struct (something like MeshBody) when dealing with imported geometries?

Part of my confusion comes down to not fully understanding the following code from the 3D Donut example below. Maybe someone can elaborate.

body = AutoBody() do xyz,t
    x,y,z = xyz - center
    norm2([x,norm2([y,z])-R])-r
end

Question about usage

I understand pretty much how to define a body using just the distance function. Makes a lot of sense. I have two major questions, however.

First, what is the impact of small errors in this function as long as the distance at the surface of the body is zero and the errors are relatively small? For instance, if I have an elliptical body and calculate distance to the surface using the distance to a surface point on a line between the point in question and the center of the ellipse, I will give a distance that is a bit longer than the actual shortest distance. What would be the effect of that? The same thing happens in the simulation of the swimming fish. If I define the fish body as the distance to the nearest point on the spine less the orthogonal thickness at that point, I will give a slightly higher distance than is geometrically correct.

Second, can I use the map function in a body to project a circle to a wing section and then just compute the distance in the projected space?

StackOverflowError

I am running the ThreeD_donut.jl example without using GPU and meet the error of StackOverflowError.

The code is as follows:

using WaterLily
using StaticArrays
function donut(p=6;Re=1e3,U=1)
    # Define simulation size, geometry dimensions, viscosity
    n = 2^p
    center,R,r = SA[n/2,n/2,n/2], n/4, n/16
    ν = U*R/Re

    # Apply signed distance function for a torus
    norm2(x) = √sum(abs2,x)
    body = AutoBody() do xyz,t
        x,y,z = xyz - center
        norm2(SA[x,norm2(SA[y,z])-R])-r
    end

    # Initialize simulation and return center for flow viz
    Simulation((2n,n,n),(U,0,0),R;ν,body),center
end

# import CUDA
# @assert CUDA.functional()
# sim,center = donut(mem=CUDA.CuArray);
sim,center = donut(); # if you don't have a CUDA GPU
sim_step!(sim, 0.2)  
# GLMakie.mesh(body_mesh(sim), color=:green)

dat = sim.flow.σ[inside(sim.flow.σ)] |> Array;
function ω_θ!(dat,sim,center=center)
    dt, a = sim.L/sim.U, sim.flow.σ
    @inside a[I] = WaterLily.ω_θ(I,(1,0,0),center,sim.flow.u)*dt
    copyto!(dat,a[inside(a)]) 
end

include("ThreeD_Plots.jl")
@time makie_video!(sim,dat,ω_θ!,name="donut.mp4",duration=1,step=0.25) do obs
    contour(obs, levels=[-5,5], colormap=:balance)
end

================================

After I run this code, the result in terminal as as follows:

simulation 0% complete
ERROR: StackOverflowError:
Stacktrace:
 [1] setindex!(A::GLMakie.GLAbstraction.Texture{ColorTypes.RGBA{Float32}, 1}, value::Vector{ColorTypes.RGBA{Float32}}, indexes::Base.Slice{Base.OneTo{Int64}})
   @ GLMakie.GLAbstraction ~/.julia/packages/GLMakie/9nid7/src/GLAbstraction/AbstractGPUArray.jl:38
 [2] setindex!(A::GLMakie.GLAbstraction.Texture{ColorTypes.RGBA{Float32}, 1}, value::Vector{ColorTypes.RGBA{Float32}}, indexes::Base.Slice{Base.OneTo{Int64}}) (repeats 79983 times)
   @ GLMakie.GLAbstraction ~/.julia/packages/GLMakie/9nid7/src/GLAbstraction/AbstractGPUArray.jl:41

But the torus could be displayed if I only run the following line instead of plotting the simulation results:

GLMakie.mesh(body_mesh(sim), color=:green)
PS:
My laptop is mac M2. Julia version is 1.9.2. And Version of packages is as follows:
(WaterLily.jl) pkg> status

Status `~/Work/research/WaterLily.jl/Project.toml`
  [e9467ef8] GLMakie v0.8.7
  [5c1252a2] GeometryBasics v0.4.9
  [e6723b4c] Meshing v0.6.0
⌃ [dde4c033] Metal v0.3.0
  [91a5bcdd] Plots v1.38.17
  [90137ffa] StaticArrays v1.6.2
  [ed894a53] WaterLily v1.0.2

Simulations of wing-like structures always have detached flow

In trying to simulate a crude wing in 2-D, I always seem to have flow that detaches from the top of the wing. This happens with low and high reynolds numbers and with low and high viscosities and very low angle of attack.

Could you help me build a simple example that does this correctly?

Here is an example:

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.