Internal Functions
Swalbe.equilibrium!
— Methodequilibrium!(feq, height, velocity, gravity)
Calculation of the equilibrium distribution feq
for the shallow water lattice Boltzmann method.
Arguments
feq :: Array{<:Number,3}
: Equilibrium distribution function, to be calculatedheight :: Array{<:Number,2}
: The height field $h(\mathbf{x},t)$velocity :: Array{<:Number,2}
: x-component of the macroscopic velocitygravity <: Number
: Strength of the gravitational acceleration in lattice units
Examples
julia> using Swalbe, Test
julia> feq = zeros(10,3); ρ = ones(10); u = fill(0.1,10);
julia> Swalbe.equilibrium!(feq, ρ, u, 0.1) # Supply dummy u^2 as well.
julia> feq[:,1]
10-element Vector{Float64}:
0.94
0.94
0.94
0.94
0.94
0.94
0.94
0.94
0.94
0.94
julia> @test all(feq[:,1] .≈ 1 - 0.1/2 - 0.01)
Test Passed
Swalbe.equilibrium!
— Methodequilibrium!(feq, height,velocityx, velocityy, vsquare, gravity)
Calculation of the equilibrium distribution feq
for the shallow water lattice Boltzmann method.
Arguments
feq :: Array{<:Number,3}
: Equilibrium distribution function, to be calculatedheight :: Array{<:Number,2}
: The height field $h(\mathbf{x},t)$velocityx :: Array{<:Number,2}
: x-component of the macroscopic velocityvelocityy :: Array{<:Number,2}
: y-component of the macroscopic velocityvsquare :: Array{<:Number,2}
: Dummy array that is preallocated to be filled with the square of the velocity vectorgravity <: Number
: Strength of the gravitational acceleration in lattice units
Mathematics
The detailed motivation and derivation can be found in the article of Salmon. Similar to the standard Navier-Stokes approximating lattice Boltzmann methods the equilibrium distribution feq
is an expansion to second order in velocity u
. If you want it is an slow mode part of the shallow water theory and thus the equilibrium is given as
$f_i^{\text{eq}} = h \bigg(1 - \frac{5}{6}g h - \frac{2}{3}u^2\bigg),\quad i=0 \newline f_i^{\text{eq}} = w_i h \bigg(g h + 3 \mathbf{c}_i\cdot\mathbf{u} + \frac{9}{2}(\mathbf{c}_i\cdot\mathbf{u})^2) + \frac{3}{2}u^2\bigg),\quad else$
where $g$ is the gravitational acceleration (in lattice units) and $w_i, \mathbf{c}_i$ are the weights and lattice velocities.
It has been shown that it is possible to get rid of the gravity driven term in the equilibrium distribution, thus
$f_i^{\text{eq}} = h \bigg(1 - \frac{2}{3}u^2\bigg),\quad i=0 \newline f_i^{\text{eq}} = w_i h \bigg(3 \mathbf{c}_i\cdot\mathbf{u} + \frac{9}{2}(\mathbf{c}_i\cdot\mathbf{u})^2) + \frac{3}{2}u^2\bigg),\quad else$
then of course the topography gradient has to be included as a force term.
Examples
julia> using Swalbe, Test
julia> feq = zeros(5,5,9); ρ = ones(5,5); ux = fill(0.1,5,5); uy = zeros(5,5);
julia> Swalbe.equilibrium!(feq, ρ, ux, uy, zeros(5,5), 0.1) # Supply dummy u^2 as well.
julia> feq[:,:,1]
5×5 Matrix{Float64}:
0.91 0.91 0.91 0.91 0.91
0.91 0.91 0.91 0.91 0.91
0.91 0.91 0.91 0.91 0.91
0.91 0.91 0.91 0.91 0.91
0.91 0.91 0.91 0.91 0.91
julia> Swalbe.equilibrium!(feq, ρ, ux, uy, zeros(5,5), 0.0) # Supply dummy u^2 as well.
julia> @test all(feq[:,:,1] .≈ 1 - 2/3 * 0.01)
Test Passed
References
Swalbe.BGKandStream!
— MethodBGKandStream!(fout, feq, ftemp, Fx, Fy, τ)
Performs a BGK collision operation with a WFM forcecorrection and a subsequent streaming of the resulting populations.
Arguments
fout :: Array{<:Number,3}
: Streamed distribution after the collision processesfeq :: Array{<:Number,3}
: Equilibrium distribution, computed with equilibrium!ftemp :: Array{<:Number,3}
: Temporary distribution from the time step before, only useful if $\tau \neq 1$Fx :: Array{<:Number,2}
: Sum of forces acting on the fluid in x-directionFy :: Array{<:Number,2}
: Sum of forces acting on the fluid in y-directionτ <: Number
: Relaxtion time, if not supplied $\tau = 1$ assumed
Mathematics
The lattice Boltzmann equation in its discretized format is relatively simple to write down
$f_{\alpha}(\mathbf{x}+\mathbf{e}_{\alpha}\Delta t, t+\Delta t) - f_{\alpha}(\mathbf{x}, t) = -\frac{1}{\tau}(f_{\alpha}(\mathbf{x}, t) - f^{\text{eq}}_{\alpha}(\mathbf{x}, t)) + \Delta t \mathcal{S}_{\alpha},$
where the collision kernel is approximated with a BKG single relaxation time (SRT)
$\Omega_{\alpha} = \frac{1}{\tau}(f_{\alpha}(\mathbf{x}, t) - f^{\text{eq}}_{\alpha}(\mathbf{x}, t),$
and a source term $\mathcal{S}$ which is given by
$\mathcal{S}_{\alpha} = \frac{3 w_{\alpha}}{e_{\alpha x}e_{\alpha x}+e_{\alpha y}e_{\alpha y}}\mathbf{e_{\alpha}}\cdot\mathbf{F}_{\alpha} .$
The term $e_{\alpha x}e_{\alpha x}+e_{\alpha y}e_{\alpha y}$ is either zero for the zeroth population, 1 for the first four populations or 2 for the remaining ones.
Examples
julia> using Swalbe
julia> feq = ones(5,5,9); ftemp = zeros(5,5,9); fout = zeros(5,5,9);
julia> feq[1,1,:] .= 2.0 # To check the streaming process
9-element view(::Array{Float64, 3}, 1, 1, :) with eltype Float64:
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
julia> Swalbe.BGKandStream!(fout, feq, ftemp, zeros(5,5), zeros(5,5), 1.0)
julia> fout[:,:,6] # The value 2 should have moved one down and one to the right!
5×5 Matrix{Float64}:
1.0 1.0 1.0 1.0 1.0
1.0 2.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0
References
See also: Swalbe.equilibrium!
Swalbe.BGKandStream!
— MethodBGKandStream!(fout, feq, ftemp, F::Vector, τ)
Performs a BGK collision operation with a WFM forcecorrection and a subsequent streaming of the resulting populations.
Arguments
fout :: Array{<:Number,3}
: Streamed distribution after the collision processesfeq :: Array{<:Number,3}
: Equilibrium distribution, computed with equilibrium!ftemp :: Array{<:Number,3}
: Temporary distribution from the time step before, only useful if $\tau \neq 1$F :: Vector{<:Number,2}
: Sum of forces acting on the fluidτ <: Number
: Relaxtion time, if not supplied $\tau = 1$ assumed
See also: Swalbe.equilibrium!
Swalbe.BGKandStream!
— MethodBGKandStream!(state, sys; τ=sys.param.τ)
Performs a BGK collision operation with a WFM forcecorrection and a subsequent streaming of the resulting populations.
Arguments
state <: LBM_state
:State
data structure, works for every state related data structuresys :: SysConst
:SysConst
data structure with τ as parameterτ :: Float64
: relaxation time, default τ=sys.param.τ
Examples
julia> using Swalbe
julia> sys = Swalbe.SysConst{Float64}(Lx=5, Ly=5, param=Swalbe.Taumucs()); state = Swalbe.Sys(sys, "CPU");
julia> state.feq[1,1,:] .= 2.0 # To check the streaming process
9-element view(::Array{Float64, 3}, 1, 1, :) with eltype Float64:
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
julia> Swalbe.BGKandStream!(state, sys)
julia> state.fout[:,:,6] # The value 2 should have moved one down and one to the right!
5×5 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0
0.0 2.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
See also: Swalbe.equilibrium!
Swalbe.viewdists
— Methodviewdists(f)
Generates a view for all nine populations of a D2Q9 distribution function.
Examples
julia> using Swalbe, Test
julia> ftest = reshape(collect(1.0:225.0),5,5,9);
julia> f0, f1, f2, f3, f4, f5, f6, f7, f8 = Swalbe.viewdists(ftest);
julia> @test all(f3 .== ftest[:,:,4])
Test Passed
See also: Swalbe.BGKandStream!
Swalbe.viewdists_1D
— Methodviewdists_1D(f)
Generates a view for all three populations of a D1Q3 distribution function.
Examples
julia> using Swalbe, Test
julia> ftest = reshape(collect(1.0:30.0),10,3);
julia> f0, f1, f2 = Swalbe.viewdists_1D(ftest);
julia> @test all(f1 .== ftest[:,2])
Test Passed
See also: Swalbe.BGKandStream!
Swalbe.moments!
— Methodmoments!(height, velx, vely, fout)
Computation of the hydrodynamic moments, height
and velocity
.
Mathematics
The macroscopic quantities such as the height and the velocity are the moments of the distribution function,
$h(\mathbf{x},t) = \sum_{i=0}^8 f_i ,$
and
$\mathbf{v}(\mathbf{x},t) = \frac{1}{h}\sum_{i=0}^8 \mathbf{c}_i f_i .$
Examples
julia> using Swalbe, Test
julia> fout = zeros(5,5,9); fout[:,:,1] .= 1.0; fout[:,:,2] .= 0.1; # Dist with artifical velocity in x
julia> height = zeros(5,5); velx = zeros(5,5); vely = zeros(5,5);
julia> Swalbe.moments!(height,velx,vely,fout)
julia> @test all(height .== 1.1)
Test Passed
julia> @test all(velx .== 0.1/1.1)
Test Passed
julia> @test all(vely .== 0.0)
Test Passed
References
Swalbe.h∇p!
— Methodh∇p!(state)
Computation of the pressure gradient multiplied with the height.
Mathematics
The pressure gradient ($\nabla p$) is the driving force of the standard thin film equation $\partial_t h = (M(h)\nabla p)$. Our approach however does not solve the thin film equation directly, Instead we have to add the pressure gradient as a force which is given as
$F_{film} = -\frac{1}{\rho_0} h \nabla p_{film},$
where the term $p_{film}$ describes the film pressure
$p_{film} = -\gamma [\Delta h - \Pi(h)] .$
As such it is the combination of a laplacian term that minimizes the surface area as well as a interfacial potential between substrate and fluid.
Examples
julia> using Swalbe, Test
julia> state = Swalbe.Sys(Swalbe.SysConst(Lx=10, Ly=10, param=Swalbe.Taumucs()), "CPU");
julia> state.pressure .= reshape(collect(1:100),10,10);
julia> Swalbe.h∇p!(state)
julia> @test all(state.h∇px[1,:] .== -4.0) # at boundary
Test Passed
julia> @test all(state.h∇px[2,:] .== 1.0) # inside
Test Passed
References
See also: Swalbe.filmpressure!
Swalbe.inclination!
— Methodinclination!(α, state)
Force that mimics the effect of an inclined plate.
Simple model to add a body force on the fluid that should mimic the effect of an inclined plate. The model includes a smoothing tanh
function to absorb shocks which might occure.
Arguments
-α :: Vector
: Force vector that both hosts direction of the force as well as strength -state::State
: Lattice Boltzmann state of the fluid, here we need the state.Fx
, state.Fy
fields -t::Int
: Time step, used for the smoothing tanh
factor -tstart::Int
: Time delay at which the tanh
becomes positve -tsmooth::Int
: Time interval over which the tanh
is smeared
Mathematics
This body force is simply force strength time the mass of the fluid or even simpler the height (assuming ρ=1). Thus the force becomes
$\mathbf{F} = \mathbf{\alpha} h \tanh\bigg(\frac{t-t_0}{t_s}\bigg),$
with $t_0$ being the time lag at which the tanh
changes sign and $t_s$ is width of interval between -1 and 1.
See also: Swalbe.run_dropletforced
Swalbe.slippage!
— Methodslippage!(slipx, slipy, height, velx, vely, δ, μ)
Fluid substrate interaction that effectively mimics a velocity boundary condition at $h=0$.
Arguments
slipx :: Array{<:Number,2}
: The x-component of the force due the velocity boundary conditionslipy :: Array{<:Number,2}
: The y-component of the force due the velocity boundary conditionheight::Array{<:Number,2}
: Height field $h(\mathbf{x},t)$velx::Array{<:Number,2}
: x-component of the macroscopic velocity vectorvely::Array{<:Number,2}
: y-component of the macroscopic velocity vectorδ <: Number
: Extrapolation length into the substrate where the no-slip is metμ <: Number
: Kinematic viscosity of the simulation, dependent on the value of τ
Mathematics
With the velocity boundary condition at the fluid substrate interface we build the second main descriptor between our model and the thin film equation. One well studied assumption is that the fluid velocity vanishes at $h(\mathbf{x}) = 0$ which is called no-slip condition. In terms of thin film mobility M(h)
this means
$M(h) = \frac{h^3}{3\mu}.$
This assumption, however, can be relaxed to allow for some slippage with a further parameter δ which determines the slip length. The here presented model matches the thin film equation by modification of the momentum equation of the shallow water model. Therefore the force we add to the momentum equation to compensate for the substrate fluid friction is given as
$F_{fric} = -\nu \alpha_{\delta}(h)\mathbf{u},$
where ν is the kinematic viscosity and α
$\alpha_{\delta}(h) = \frac{6h}{2h^2 + 6h\delta +3\delta^2},$
and δ being the slip length, or the distance inside the substrate where the fluids velocity vanishes.
References
Swalbe.thermal!
— Methodthermal!(fx, fy, height, kᵦT, μ, δ)
Computations of force due to thermal fluctuations.
Arguments
fx :: Array{<:Number,2}
: x-component of the force due to the fluctuationsfy :: Array{<:Number,2}
: y-component of the force due to the fluctuationsheight :: Array{<:Number,2}
: Height field $h(\mathbf{x},t)$kᵦT <: Number
: Strenght of thermal fluctuations in lattice unitsμ <: Number
: The kinetic viscosityδ <: Number
: Slip length, needed to normalize
Mathematics
The classical thin film equation is an equation without thermal noise which is defined as
$\partial_t h = \nabla\cdot(M(h)\nabla p).$
Classically thermal excitations are neglected for thin film flows. It is of course possible to introduce for example an envolving surface tension or viscosity but both do not account for e.g. the spectrum of thermal capillary waves. The reason for this short coming is the complex from the thin film equation takes if derived from the Landau-Lifshitz Navier-Stokes equation. One addition term due to the stochastic stress tensor makes it somewhat impossible to solve the eqaution self-consistently. However Grün et al. showed that it is not nessary to use the full stochastic stress tensor, but simple a multiplicative noise term
$\partial_t h = \nabla\cdot[M(h)(\nabla p + \sigma \mathcal{N}],$
where $\sigma$ and $\mathcal{N}$ are a dimensionless temperture and a gaussian white noise. Similar to the pressure gradient the addition of this term is introduced as force in our model
$F_{fluc} = \frac{1}{\rho_0}\sqrt{2k_BT\mu\alpha_{\delta}(h)}\mathcal{N},$
with $\alpha_{\delta}(h)$ being the force generated due to substrate slip.
Examples
julia> using Swalbe, Statistics, Test, Random
julia> Random.seed!(1234); # Set a seed
julia> x = ones(50,50); y = ones(50,50); h = ones(50,50);
julia> Swalbe.thermal!(x, y, h, 0.1, 1/6, 1)
julia> @test mean(x) ≈ 0.0 atol=1e-2
Test Passed
julia> @test mean(y) ≈ 0.0 atol=1e-2
Test Passed
julia> @test var(x) ≈ 2*0.1/11 atol=(2*0.1/11)/10 # var = 2kbt*6*μ/slip
Test Passed
References
See also: Swalbe.slippage!
Swalbe.update_rho!
— Methodupdate_rho()
Time evolution of the active
field rho.
TODO: @Tilman!
Swalbe.view_four
— Methodview_four()
Splits a chuck of memory in four equivalent chucks
Swalbe.∇γ!
— Methodsurface_tension_gradient!(state)
Computes the gradient of a spatially resolved surface tension field.
Swalbe.fast_32
— Methodfast_32(arg)
Quick computation of a power law potential see filmpressure!
Swalbe.fast_93
— Methodfast_93(arg)
Quick computation of a power law potential see filmpressure!
Swalbe.filmpressure!
— Methodfilmpressure!(pressure, height, γ, θ, n, m, hmin, hcrit)
Calculation of the capillary pressure which is given by $p = - γ∇²h+ Π(h)$.
Arguments
pressure :: Array{Number,2}
: Array that store the result of the compuationheight :: Array{Number,2}
: Height field $h(\mathbf{x},t)$γ <: Number
: Forcing strenght due to surface tensionθ <: Number
: Equilibrium contact anglen :: Int
: Larger power law exponent for $Π(h)$m :: Int
: Smaller power law exponent for $Π(h)$hmin <: Number
: Parameter of $Π(h)$, in fact $Π(hmin) = 0$hcrit <: Number
: Numerical stabilizer for case $h(\mathbf{x},t) \ll hmin$
Mathematics
The capillary pressure $p_{\text{cap}}$ is the centeral angle to match our model with the thin film equation. It consists of two parts, first being the laplace pressure $\nabla^2 h$ and second being the derivative of the disjoining pontential $\Pi(h)$
$p_{\text{cap}} = -\gamma \nabla^2 h + \Pi(h).$
For the laplacian term we use the same nine point discretization as in Swlabe.∇²f!
. $\Pi(h)$ on the other hand is given by
$\Pi(h) = \kappa(\theta)f(h),$
where $\kappa(\theta)$ is simply a measure for the Hamaker constant and given as
$\kappa(\theta) = \gamma(1- \cos(\theta))\frac{(n-1)(m-1)}{(n-m)h_{\text{min}}}.$
For $f(h)$ one can use various forms, a very common however is the power law given by
$f(h) = \bigg[\bigg(\frac{h_{\text{min}}}{h}\bigg)^n - \bigg(\frac{h_{\text{min}}}{h}\bigg)^m\bigg].$
Examples
julia> using Swalbe, Test
julia> h = reshape(collect(1.0:25.0),5,5) # A dummy height field
5×5 Matrix{Float64}:
1.0 6.0 11.0 16.0 21.0
2.0 7.0 12.0 17.0 22.0
3.0 8.0 13.0 18.0 23.0
4.0 9.0 14.0 19.0 24.0
5.0 10.0 15.0 20.0 25.0
julia> pressure = zeros(5,5); θ = 0.0; # Fully wetting substrate
julia> Swalbe.filmpressure!(pressure, h, zeros(5,5,8), 0.01, 0.0, 3, 2, 0.1, 0.05) # default γ = 0.01
julia> result = [30.0 5.0 5.0 5.0 -20;
25.0 0.0 0.0 0.0 -25.0;
25.0 0.0 0.0 0.0 -25.0;
25.0 0.0 0.0 0.0 -25.0;
20.0 -5.0 -5.0 -5.0 -30.0];
julia> for i in eachindex(result)
@test result[i] .≈ -100 .* pressure[i] atol=1e-12
end
References
Swalbe.power_2
— Methodpower_2(arg)
Power two (arg^2) computation of a Float64 number.
Swalbe.power_3
— Methodpower_3(arg)
Power three (arg^3) computation of a Float64 number.
Swalbe.power_broad
— Methodpower_broad(arg, n)
Computes arg
to the power n
.
Actually this is useful because the ^
operator is much slower. Same thing I learned about the pow
function in C, * yes it does what you want, but it is slow as fuck *.
Examples
julia> using Swalbe, Test
julia> Swalbe.power_broad(3, 3)
27
julia> Swalbe.power_broad.([2.0 5.0 6.0], 2) # Use the broadcasting operator `.`
1×3 Matrix{Float64}:
4.0 25.0 36.0
See also: filmpressure!
Swalbe.viewneighbors
— Methodviewneighbors(f)
Generates a view for all nine populations of a D2Q9 distribution function.
Examples
julia> using Swalbe, Test
julia> ftest = reshape(collect(1.0:5*5*8),5,5,8);
julia> f1, f2, f3, f4, f5, f6, f7, f8 = Swalbe.viewneighbors(ftest);
julia> @test all(f3 .== ftest[:,:,3])
Test Passed
See also: Swalbe.∇f!
, Swalbe.filmpressure!
Swalbe.viewneighbors_1D
— Methodviewneighbors_1D(f)
Generates a view for the two neighbors of a D1Q3 distribution function.
Examples
julia> using Swalbe, Test
julia> ftest = reshape(collect(1.0:30),10,3);
julia> f1, f2 = Swalbe.viewneighbors_1D(ftest);
julia> @test all(f2 .== ftest[:,2])
Test Passed
See also: Swalbe.∇f!
, Swalbe.filmpressure!
Swalbe.∇f!
— Method∇f!(outputx, outputy, f)
Gradient calculation with finite differences.
Computes both spatial first derivatives with a nine point stencil from an input f
and writes the result to outputx
and outputy
. Since broadcasting is simple on the GPU we make use of circshift
for the neighbors.
Mathematics
The gardient in two dimensions is given as
$\nabla f = \big(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\big)^T .$
Again with the nine point stencil this reduces to
$\frac{\partial f}{\partial x} = \frac{1}{3} (f_{i+1,j} - f_{i-1,j}) + \frac{1}{12}(f_{i+1,j+1} - f_{i-1,j+1} - f_{i-1,j-1} + f_{i+1,j-1}) ,$
and for the y
component we get
$\frac{\partial f}{\partial y} = \frac{1}{3} (f_{i,j+1} - f_{i,j-1}) + \frac{1}{12}(f_{i+1,j+1} + f_{i-1,j+1} - f_{i-1,j-1} - f_{i+1,j-1}) .$
For the exact derivation feel free to read the reference by Junk and Klar.
Examples
julia> using Swalbe, Test
julia> arg = reshape(collect(1.0:25),5,5)
5×5 Matrix{Float64}:
1.0 6.0 11.0 16.0 21.0
2.0 7.0 12.0 17.0 22.0
3.0 8.0 13.0 18.0 23.0
4.0 9.0 14.0 19.0 24.0
5.0 10.0 15.0 20.0 25.0
julia> resx = zeros(5,5); resy = zeros(5,5); Swalbe.∇f!(resx, resy, arg)
julia> whatXshouldbe = [-1.5 -1.5 -1.5 -1.5 -1.5;
1.0 1.0 1.0 1.0 1.0;
1.0 1.0 1.0 1.0 1.0;
1.0 1.0 1.0 1.0 1.0;
-1.5 -1.5 -1.5 -1.5 -1.5];
julia> for i in eachindex(resx) # Test the x-component
@test resx[i] ≈ whatXshouldbe[i] atol=1e-10
end
julia> whatYshouldbe = [-7.5 5.0 5.0 5.0 -7.5;
-7.5 5.0 5.0 5.0 -7.5;
-7.5 5.0 5.0 5.0 -7.5;
-7.5 5.0 5.0 5.0 -7.5;
-7.5 5.0 5.0 5.0 -7.5];
julia> for i in eachindex(resy) # Test the y-component
@test resy[i] ≈ whatYshouldbe[i] atol=1e-10
end
References
See also: Swalbe.∇²f!
Swalbe.∇²f!
— Method∇²f!(output, f, γ)
Finite difference operator for a second derivative in two dimensions.
Computes the laplacian of an input f
times a scalar γ
and stores the result in output
.
Mathematics
The laplacian operator in two dimensions can be written as
$\nabla^2 f = \frac{\partial^2 f}{\partial x^2} + \frac{\partial^2 f}{\partial y}.$
For the discretization of this operator we use a nine point stencil, such the neighbors as well as the diagonal elements. The concrete derivation can be found in the references below, we just show the final result
$\nabla^2 f = \frac{1}{6}\bigg[4(f_{i+1,j} + f_{i,j+1} + f_{i-1,j} + f_{i,j-1}) \newline \qquad\qquad +(f_{i+1,j+1} + f_{i-1,j+1} + f_{i-1,j-1} + f_{i+1,j-1}) \newline \qquad\qquad -20f_{i,j}\bigg] ,$
where we have used Julia conventions, downwards (left) is positive. The whole expression can be multiplied with a scalar γ
if needed.
Examples
julia> using Swalbe, Test
julia> arg = reshape(collect(1.0:25),5,5)
5×5 Matrix{Float64}:
1.0 6.0 11.0 16.0 21.0
2.0 7.0 12.0 17.0 22.0
3.0 8.0 13.0 18.0 23.0
4.0 9.0 14.0 19.0 24.0
5.0 10.0 15.0 20.0 25.0
julia> res = zeros(5,5); Swalbe.∇²f!(res, arg, -1.0)
julia> analytics = [-30.0 -5.0 -5.0 -5.0 20;
-25.0 0.0 0.0 0.0 25.0;
-25.0 0.0 0.0 0.0 25.0;
-25.0 0.0 0.0 0.0 25.0;
-20.0 5.0 5.0 5.0 30.0];
julia> for i in eachindex(analytics)
@test analytics[i] ≈ res[i] atol=1e-10
end
References
See also: Swalbe.∇f!
Swalbe.CuState
— TypeCuState
State
data structure but for CUDA like memory.
Similar to CuState_thermal
without the thermal fields.
Arguments
fout :: CuArray{T,N}
: Output distribution functionftemp :: CuArray{T,N}
: Temporary distribution function, only used ifsys.τ ≠ 1
feq :: CuArray{T,N}
: Equilibrium distribution functionheight :: CuArray{T}
: Field that stores the scalar height valuesvelx :: CuMatrix{T}
: Field that stores the x-component of the velocity vectorvely :: Matrix{T}
: Field that stores the y-component of the velocity vectorvsq :: Matrix{T}
: Field that stores the velocity squared, used inequilibrium!
pressure :: Matrix{T}
: Pressure distribution computed using thefilmpressure!
functionFx :: Matrix{T}
: Total force acting on the fluid, x-componentFy :: Matrix{T}
: Total force acting on the fluid, y-componentslipx :: Matrix{T}
: Friction force due to substrate slip, x-componentslipy :: Matrix{T}
: Friction force due to substrate slip, y-componenth∇px :: Matrix{T}
: Pressure gradient times the height, x-componenth∇py :: Matrix{T}
: Pressure gradient times the height, y-componentdgrad :: Array{T,N}
: Dummy allocation to store shifted arrays usingcircshift!
Swalbe.State
— TypeState{T, N}
Data structure for both macroscopic variables and distribution functions.
Arguments
fout :: Array{T,N}
: Output distribution functionftemp :: Array{T,N}
: Temporary distribution function, only used ifsys.τ ≠ 1
feq :: Array{T,N}
: Equilibrium distribution functionheight :: Matrix{T}
: Field that stores the scalar height valuesvelx :: Matrix{T}
: Field that stores the x-component of the velocity vectorvely :: Matrix{T}
: Field that stores the y-component of the velocity vectorvsq :: Matrix{T}
: Field that stores the velocity squared, used inequilibrium!
pressure :: Matrix{T}
: Pressure distribution computed using thefilmpressure!
functionFx :: Matrix{T}
: Total force acting on the fluid, x-componentFy :: Matrix{T}
: Total force acting on the fluid, y-componentslipx :: Matrix{T}
: Friction force due to substrate slip, x-componentslipy :: Matrix{T}
: Friction force due to substrate slip, y-componenth∇px :: Matrix{T}
: Pressure gradient times the height, x-componenth∇py :: Matrix{T}
: Pressure gradient times the height, y-componentdgrad :: Array{T,N}
: Dummy allocation to store shifted arrays usingcircshift!
See also: State_thermal{T, N}
, CuState
, State_1D{T, N}
Swalbe.StateWithBound_1D
— TypeStateWithBound_1D{T, N}
Data structure that stores all arrays for a given simulation.
Swalbe.State_1D
— TypeState_1D{T, N}
Data structure that for a one dimensional simulation.
Arguments
fout :: Matrix{T}
: Output distribution functionftemp :: Matrix{T}
: Temporary distribution function, only used ifsys.τ ≠ 1
feq :: Matrix{T}
: Equilibrium distribution functionheight :: Vector{T}
: Field that stores the scalar height valuesvel :: Vector{T}
: Field that stores the velocitypressure :: Vector{T}
: Pressure distribution computed using thefilmpressure!
functionF :: Vector{T}
: Total force acting on the fluidslip :: Vector{T}
: Friction force due to substrate sliph∇p :: Vector{T}
: Pressure gradient times the heightdgrad :: Matrix{T}
: Dummy allocation to store shifted arrays usingcircshift!
Swalbe.State_gamma_1D
— TypeState_gamma_1D{T, N}
State_1D
data structure with additional fields for surface tension γ
and ∇γ
.
Arguments
basestate :: State_1D{T}
: Base data structure for one dimensional simulationsγ :: Vector{T}
: Surface tension field∇γ :: Vector{T}
: Surface tension gardient
Swalbe.State_thermal
— TypeState_thermal{T, N}
Data structure that contains a State
and fields for thermal fluctuations.
Arguments
basestate :: State{T,N}
: State data structerkbtx :: Matrix{T}
: Force due to thermal fluctuations, x-componentkbty :: Matrix{T}
: Force due to thermal fluctuations, y-component
Swalbe.State_thermal_1D
— TypeState_thermal_1D{T, N}
State_1D
data structure with additional fields for thermal fluctuations (noise).
Arguments
basestate :: State_1D{T}
: Base data structure for one dimensional simulationskbt :: Vector{T}
: Surface tension field
Swalbe.SysConst
— TypeSysConst{T}
Struct that contains the system size of a two dimensional system and the struct Taumucs
.
Arguments
Lx :: Int
: Length of the lattice sides in x-directionLy :: Int
: Length of the lattice sides in y-directions :: Taumucs
: Most of the run time constants
See also: SysConst_1D{T}
, Taumucs{T}
Swalbe.SysConstWithBound_1D
— TypeSysConstWithBound_1D{T}
Struct that contains all run time constants, e.g. lattice size, surface tension γ
and so on.
Arguments
Swalbe.SysConst_1D
— TypeSysConst_1D{T}
Struct that contains the system size of a one dimensional system and the struct Taumucs
.
Arguments
L :: Int
: Number of lattice pointss :: Taumucs
: Most of the run time constants
See also: SysConst{T}
, Taumucs{T}
Swalbe.Taumucs
— TypeTaumucs{T}
Struct that contains most run time constants, e.g. surface tension γ
, viscosity μ
and so on.
Arguments
Tmax :: Int
: Number of lattice Boltzmann time iterationstdump :: Int
: Dumping interval for e.g. data outputτ :: T
: BGK relaxation ratecₛ :: T
: Lattice speed of sound, every physical velocity needs to be smaller than this!μ :: T
: Kinematic fluid viscosityδ :: T
: Slip length, defines how far the no-slip condition is interpolated into the substratekbt :: T
: Thermal energy of the film, works with small values ≈ 10^(-7)γ :: T
: Surface tensionn :: Int
: Greater exponent of the two used for the powerlaw in the disjoining pressurem :: Int
: Smaller exponent of the two used for the powerlaw in the disjoining pressurehmin :: T
: Height value at which the disjoining pressure functional vanisheshcrit :: T
: Numerical stabilizer for the disjoining pressure termθ :: T
: Contact angle in multiples of πg :: T
: gravitational acceleration, usually neglected in thin film simulations
See also: SysConst{T}
, SysConst_1D{T}
Swalbe.Sys
— MethodSys(sysc, T, kind)
Allocations of arrays used to run a one dimensional simulation.
Returns a State
data structure based on kind
the struct can be "simple", "thermal" or "gamma".
Arguments
sysc :: SysConst_1D
: Stores the lattice sizeL
T <: Number
: Optional numerical type, default is set toFloat64
kind :: String
: Optional, default is set tosimple
Swalbe.Sys
— MethodSys(sysc, device, exotic, T)
Mostly allocations of arrays used to run a simulation, but all within one function :)
Returns not a data structure such as state, but every array. Therefore it is somewhat outdated by now (2022).
Arguments
sysc :: SysConst
: Needed for the lattice dimensions,Lx
andLy
device :: String
: Use eitherCPU
for computation of a CPU orGPU
for computation on the GPUexotic :: Bool
: If true thermal fluctuations can be computed and saved to thefthermalx
andfthermaly
fieldT <: Number
: Numerical type, it is strongly suggested to useFloat64
Swalbe.Sys
— MethodSys(sysc, device; T, kind)
Allocations of arrays used to populate the State
data structure.
Returns a State
data structure based on sysc
, either one dimensional or two dimensional.
Arguments
sysc :: SysConst
: Needed for the lattice dimensions,Lx
andLy
device :: String
: Use eitherCPU
for computation of a CPU orGPU
for computation on the GPU (GPU can only be used with a two dimensional system)T <: Number
: Numerical type, it is strongly suggested to useFloat64
kind :: String
: Indicator for differentState
's, default value is "simple" which creates aState
data structure, valid options are ["simple", "thermal"]
Swalbe.obslist!
— Methodfunction obslist1D!(sys::SysConstWithBound; T=Float64, verbose=false)
Updates interior and border according to obs. See obslist().
Swalbe.obslist1D
— Methodfunction obslist1D(obs) Returns a list containing the relevgant obstacle nodes constructed from interior, obsleft, obsright, obsup, obsdown, corneroutlu, corneroutld, corneroutru, corneroutrd, cornerru, cornerrd, cornerlu, cornerld
Swalbe.boxpattern
— Methodboxpattern(θ, θ₀; center=(size(θ,1)÷2, size(θ,2)÷2), δₐ=1/36, side=20)
Defines a quadratic box around a center
with side length side
.
Arguments
θ::Array{undef,2}
: Contact angle mapθ₀
: Default contact angleθ
center::Tuple{Int, Int}
: center of the box pattern, default value is(size(θ,1)÷2, size(θ,2)÷2)
δₐ::Float64
: Contact angle contrast with the substrate, default is set to1/36
≈ 5 degrees differenceside::Int
: Length of the sides, default is set to20
Examples
julia> using Swalbe
julia> θ₀ = 1/9;
julia> pattern, polygon = Swalbe.boxpattern(ones(100,100), θ₀);
julia> polygon # Some cool thing we use to create the posize(θ,2)gones, a LazySet
LazySets.VPolygonModule.VPolygon{Float64, Vector{Float64}}([[40.0, 40.0], [60.0, 40.0], [60.0, 60.0], [40.0, 60.0]])
julia> pattern[50,50] == pattern[1,1] # In the center there is a different contact angle!
false
References
Really not much to say here, check out LazySets.jl.
Swalbe.ellipsepattern
— Methodellipsepattern(θ, θ₀; center=(size(θ,1)÷2, size(θ,2)÷2), δₐ=1/36, a=10, b=5)
Creates an ellipse shaped contact angle defect with contact angle mismatch δₐ
.
Arguments
θ::Array{undef,2}
: Contact angle mapθ₀
: Default contact angleθ
center::Tuple{Int, Int}
: center of the created pattern, default values iscenter = (size(θ,1)÷2, size(θ,2)÷2)
δₐ::Float64
: contact angle mismatch between patch and rest of substrate, default isδₐ = 1/36
or 5 degreesa::Int
:: semimajor half ax of the ellipse, default value isa=10
b::Int
:: semiminor half ax of the ellipse, default value isb=5
Examples
julia> using Swalbe, Test
julia> θ₀ = 1/9;
julia> θ, P = Swalbe.ellipsepattern(ones(100,100), θ₀); # per default the center is in the middle!
julia> @test θ[1,1] == θ₀
Test Passed
julia> @test θ[50,50] == θ₀ + 1/36 # The default increment, is about 5 degrees.
Test Passed
References
Nothing interesting here.
Swalbe.randinterface!
— Methodrandinterface!(height, h₀, ϵ)
Creates a random height field with average height h₀
and displacement magnitude ϵ
.
Swalbe.restart_from_height
— Methodrestart_from_height(data)
Restarts a simulation from already generated height data.
Arguments
data::file
: Some file with computed datakind::String
: File format,.bson
or.jld2
are valid optionstimestep::Int
: The time step the height is read from filesize::Tuple{Int,Int}
: x and y limits of the computational domain
julia> using Swalbe, FileIO, Test
julia> h1 = rand(10,10); h2 = rand(10,10);
julia> f = Dict() # For storage
Dict{Any, Any}()
julia> f["h_1"] = vec(h1); f["h_2"] = vec(h2);
julia> save("file.jld2", f)
julia> h = Swalbe.restart_from_height("file.jld2", timestep=1, size=(10,10));
julia> @test all(h .== h1)
Test Passed
julia> rm("file.jld2")
Swalbe.sinewave1D!
— Methodsinewave1D!(height, h₀, n, ϵ)
Creates a sine wave like height field with n
full waves, average height h₀
and displacement magnitude ϵ
.
Swalbe.singledroplet
— Methodsingledroplet(T, size(θ,1), size(θ,2), radius, θ, center, device=false)
Generates a fluid configuration of a single droplet in the shape of spherical cap with contact angle θ
, sphere radius radius
and centered at center
.
Arguments
height::Array{undef, 2}
: numerical formate, eitherFloat64
orFloat32
radius::AbstractFloat
: radius of the underlying sphere from which the spherical cap is cut offθ::AbstractFloat
: contact angle in multiples ofπ
center::Tuple{Int,Int}
: x and y coordinates of the center of the droplet
Examples
julia> using Swalbe, Test
julia> rad = 50; θ = 1/3;
julia> height = Swalbe.singledroplet(ones(100,100), rad, θ, (50,50));
julia> @test maximum(height) == rad * (1 - cospi(θ)) # Simple geometry
Test Passed
julia> argmax(height) # Which is constistent with the center!
CartesianIndex(50, 50)
References
See also:
Swalbe.trianglepattern
— Methodtrianglepattern(θ, θ₀; center=(size(θ,1)÷2, size(θ,2)÷2), δₐ=1/36, side=60)
Generates an equilateral triangle centered around center
with contact angle contrast δₐ
and side length side
.
Arguments
θ::Array{undef,2}
: Contact angle mapθ₀
: Default contact angleθ
center::Tuple(Int, Int)
: position of the center of the triangleδₐ::Float64
: contact angle contrast with the rest of the substrateside::Int
: length of the sides of the equilateral triangle
Examples
julia> using Swalbe
julia> θ, P = Swalbe.trianglepattern(ones(50,50), 1/9, side=20) # Returns a polygon and the contact angle field
([0.1111111111111111 0.1111111111111111 … 0.1111111111111111 0.1111111111111111; 0.1111111111111111 0.1111111111111111 … 0.1111111111111111 0.1111111111111111; … ; 0.1111111111111111 0.1111111111111111 … 0.1111111111111111 0.1111111111111111; 0.1111111111111111 0.1111111111111111 … 0.1111111111111111 0.1111111111111111], LazySets.VPolygonModule.VPolygon{Float64, Vector{Float64}}([[15.0, 19.226497308103742], [35.0, 19.226497308103742], [25.0, 36.547005383792516]]))
julia> P
LazySets.VPolygonModule.VPolygon{Float64, Vector{Float64}}([[15.0, 19.226497308103742], [35.0, 19.226497308103742], [25.0, 36.547005383792516]])
julia> θ[25,25]
0.1388888888888889
References
See also:
Swalbe.two_droplets
— Methodtwo_droplets(sys)
Generates a fluid configuration of a two droplets in the shape of spherical cap with contact angles θ₁
, θ₂
, sphere radius r₁
, r₂
and centers at center
.
This is work in progress, therefore so far it is only available for the lower dimension model.
Arguments
r₁::Float64
: radius of the first sphere from which the cap is cut ofr₂::Float64
: radius of the second sphere from which the cap is cut ofθ₁::Float64
: contact angle of the first cap in multiples ofπ
θ₂::Float64
: contact angle of the second cap in multiples ofπ
center::Tuple{Int,Int}
: x coordinates of the centers of the droplets
Examples
julia> using Swalbe, Test
julia> rad = 45; θ = 1/4; sys = Swalbe.SysConst_1D(L=200, param=Swalbe.Taumucs());
julia> height = Swalbe.two_droplets(sys, r₁=rad, r₂=rad, θ₁=θ, θ₂=θ);
julia> @test maximum(height) ≈ rad * (1 - cospi(θ)) atol=0.01 # Simple geometry
Test Passed
References
See also:
Swalbe.fluid_dry!
— Methodfluid_dry!(fluid, dummy, height, t; hthresh = 0.055)
Tracks the location of the thin film as a boolean field.
Swalbe.snapshot!
— Methodsnapshot!(snap, field, t; dumping)
Makes a copy of the current state of an input array in
and saves the vectorized values as a column to out
.
Function that fills a preallocated array out with a time series of system snap shots of e.g. the height field h
.
Arguments
snap :: Array{Number,2}
: Array that stores the snap shots as columnsfield :: Array{Number,2}
: Input argument, e.g. $h(\mathbf{x},t)$t :: Int
: The current time stepdumping :: Int
: Sampling frequency
Examples
julia> using Swalbe, Test
julia> h1 = reshape(collect(1:25),5,5); h2 = reshape(collect(5:5:125),5,5);
julia> snapshot = zeros(2, 25);
julia> Swalbe.snapshot!(snapshot,h1,10,dumping=10)
julia> Swalbe.snapshot!(snapshot,h2,20,dumping=10)
julia> @test all(h1 .== reshape(snapshot[1,:],5,5))
Test Passed
julia> @test all(h2 .== reshape(snapshot[2,:],5,5))
Test Passed
References
See also: The scripts folder
Swalbe.surfacearea!
— Methodsurfacearea!(area_lv, red_energy, height, θ, ∇hx, ∇hy, dgrad, surface,)
Measures the surface area of the liquid vapor interface and the reduced surface energy.
Arguments
area_lv :: Vector{Float64}
: array to store the computed liquid vapor areared_energy :: Vector{Float64}
: array the stores the computed reduced surface energyheight :: Matrix{Float64}
: current height configurationθ :: Matrix{Float64}
: contact angle field distribution∇hx :: Matrix{Float64}
: height gradient x-component∇hy :: Matrix{Float64}
: height gradient y-componentdgrad :: Array{Float64,3}
: dummy array to store derivativessurface :: Matrix{Float64}
: array that computes locally the liquid vapor surface areat :: Int
: current time stephthresh :: Float64
: height threshold below which the substrate is considered dry
Swalbe.t0
— Methodt0(;hᵦ=0.07, γ=0.01, μ=1/6, θ=1/6)
Computes a characteristic time scale for an spinodally dewetting film.
Arguments
hᵦ :: Float64
: height at which the disjoining pressure vanishesγ :: Float64
: surface tension valueμ :: Float64
: kinematic viscosity, same as dynamic for ρ=1θ :: Float64
: highest contact angle given as radiant, e.g. θ=π/9 for 20 degrees
Mathematics
The charateristic time scale $t_0$ is set using the surface tension as well as the disjoining pressure $\Pi(h)$ as
$t_0 = \frac{3}{\gamma h_0^3 q_0^4}, \quad q_0 = \frac{\Pi'(h_0)}{2\gamma},$
where $\Pi'(h_0) = \frac{\partial\Pi}{\partial h}\bigg|_{h_0}$ is the derivative of the disjoining pressure with respect to some characteristic height.
References
Swalbe.wetted!
— Methodwetted!(area_size, drop_pos, maxheight, height, t; hthresh = 0.055)
Measures the wetted area and maximal height of the film at time step t
.
Swalbe.∇f_simple!
— Method∇f_simple!(outputx, outputy, f, dgrad)
Simple gradient calculation for the differential surface area.
Swalbe.run_dropletforced
— Methodrun_dropletforced()
Simulates a sliding droplet
Swalbe.run_dropletpatterned
— Methodrun_dropletpatterned()
Simulates an droplet on a patterned substrate
Swalbe.run_dropletrelax
— Methodrun_dropletrelax()
Simulates an out of equilibrium droplet
Swalbe.run_flat
— Methodrun_flat(Sys::SysConst, device::String)
Performs a simulation of an flat interface without forces.
Arguments
verbos :: Bool
: Enables consol outputT :: AbstractFloat
: Precision of output, defaultFloat64
Theory
Nothing at all should happen. As the initial state is falt and no force is applied the fluid has no way to flow. The equality $h(\mathbf{x},0) = h(\mathbf{x},\infty),$ should be satisfied for arbitrary many time steps.
Examples
julia> using Swalbe, Test
julia> sys = Swalbe.SysConst(Lx=100, Ly=100, Tmax=5000);
julia> h = Swalbe.run_flat(sys, "CPU", verbos=false);
julia> @test all(h.height .== 1.0) # Check if all height values are identical to 1.0 (initial condition)
Test passed
Swalbe.run_gamma
— Methodrun_(sys::Swalbe.SysConst_1D,
gamma::Vector;
r₁=115,
r₂=115,
θ₀=1/9,
verbos=true,
dump = 100,
fluid=zeros(sys.param.Tmax÷dump, sys.L))
Lattice Boltzmann simulation of coalescing droplets.
Swalbe.run_random
— Methodrun_random(sys::SysConst, device::String)
Simulation of an random undulated interface
Arguments
h₀ :: Float
: Average initial heightϵ :: Float
: Amplitude of the flucutationverbos :: Bool
: Enables consol outputT :: AbstractFloat
: Precision of output, defaultFloat64
Theory
Initial randomly perturbed fluid surface. Unstable wavemodes should grow while wavemodes larger q₀ should be damped out. Measuring this is on the TODO list.
Examples
julia> using Swalbe, Test
julia> sys = Swalbe.SysConst(Lx=100, Ly=100, Tmax=5000);
julia> Swalbe.randinterface!(height, h₀, ϵ)
julia> h = Swalbe.run_random(sys, "CPU", h₀=10, ϵ=0.1, verbos=false);
Swalbe.run_rayleightaylor
— Methodrun_rayleightaylor(sys::SysConst, device::String)
Simulation of an random undulated interface and a gravitanional pull.
Arguments
kx :: Int
: wavemode in x-direction, kx=18 -> 18 sine waves fitting into the domainky :: Int
: wavemode in y-directionh₀ :: Float
: Average initial heightϵ :: Float
: Amplitude of the flucutationverbos :: Bool
: Enables consol outputT :: AbstractFloat
: Precision of output, defaultFloat64
Theory
Initial randomly perturbed fluid surface hanging from a substrate. Here we have an interplay between gravity and surface tension. The critical wavemode can be computed according to $q_0 =$ Measuring this is on the TODO list.
Examples
julia> using Swalbe, Test
julia> sys = Swalbe.SysConst(Lx=100, Ly=100, Tmax=5000);
julia> Swalbe.randinterface!(height, h₀, ϵ)
julia> h = Swalbe.run_random(sys, "CPU", h₀=10, ϵ=0.1, verbos=false);
Swalbe.time_loop
— Methodtime_loop(sys, state)
Time stepping procedure for the lattice Boltzmann state state
given parameters sys
Swalbe.CuState
Swalbe.State
Swalbe.StateWithBound_1D
Swalbe.State_1D
Swalbe.State_gamma_1D
Swalbe.State_thermal
Swalbe.State_thermal_1D
Swalbe.SysConst
Swalbe.SysConstWithBound_1D
Swalbe.SysConst_1D
Swalbe.Taumucs
Swalbe.BGKandStream!
Swalbe.BGKandStream!
Swalbe.BGKandStream!
Swalbe.Sys
Swalbe.Sys
Swalbe.Sys
Swalbe.boxpattern
Swalbe.ellipsepattern
Swalbe.equilibrium!
Swalbe.equilibrium!
Swalbe.fast_32
Swalbe.fast_93
Swalbe.filmpressure!
Swalbe.fluid_dry!
Swalbe.h∇p!
Swalbe.inclination!
Swalbe.moments!
Swalbe.obslist!
Swalbe.obslist1D
Swalbe.power_2
Swalbe.power_3
Swalbe.power_broad
Swalbe.randinterface!
Swalbe.restart_from_height
Swalbe.run_dropletforced
Swalbe.run_dropletpatterned
Swalbe.run_dropletrelax
Swalbe.run_flat
Swalbe.run_gamma
Swalbe.run_random
Swalbe.run_rayleightaylor
Swalbe.sinewave1D!
Swalbe.singledroplet
Swalbe.slippage!
Swalbe.snapshot!
Swalbe.surfacearea!
Swalbe.t0
Swalbe.thermal!
Swalbe.time_loop
Swalbe.trianglepattern
Swalbe.two_droplets
Swalbe.update_rho!
Swalbe.view_four
Swalbe.viewdists
Swalbe.viewdists_1D
Swalbe.viewneighbors
Swalbe.viewneighbors_1D
Swalbe.wetted!
Swalbe.∇f!
Swalbe.∇f_simple!
Swalbe.∇²f!
Swalbe.∇γ!