Forcing
VortexPasta.Forcing
— ModuleForcing
Defines methods for injecting and dissipating the energy generated by a system of vortices.
Forcing methods
VortexPasta.Forcing.NoForcing
— TypeNoForcing() <: AbstractForcing
Represents the absence of external forcing.
VortexPasta.Forcing.NormalFluidForcing
— TypeNormalFluidForcing <: AbstractForcing
NormalFluidForcing(vn::Function; α, α′ = 0)
Forcing due to mutual friction with a normal fluid.
The normal fluid is represented by a function vn
which should take a position x⃗::SVector{N, T}
and return a velocity v⃗::SVector{N, T}
(N
is the number of dimensions, usually N = 3
).
In particular, the function could be a synthetic velocity field from the SyntheticFields
module (see below for examples).
This type of forcing defines an external velocity $\bm{v}_{\text{f}}$ affecting vortex motion, so that the actual vortex velocity $\bm{v}_{\text{L}}$ is
\[\frac{\mathrm{d}\bm{s}}{\mathrm{d}t} = \bm{v}_{\text{L}} = \bm{v}_{\text{s}} + \bm{v}_{\text{f}}.\]
Here $\bm{v}_{\text{s}}$ is the self-induced velocity obtained by applying Biot–Savart's law.
The forcing velocity is of the form:
\[\bm{v}_{\text{f}} = α \bm{s}' × \bm{v}_{\text{ns}} - α' \bm{s}' × \left( \bm{s}' × \bm{v}_{\text{ns}} \right)\]
where $\bm{s}'$ is the local unit tangent vector, $\bm{v}_{\text{ns}} = \bm{v}_{\text{n}} - \bm{v}_{\text{s}}$ is the local slip velocity, and $α$ and $α'$ are non-dimensional coefficients representing the intensity of Magnus and drag forces.
This "forcing" generally affects all lengthscales, even when $\bm{v}_{\text{n}}$ is a large-scale flow. This may be unwanted for turbulence simulations. In that case, the alternative FourierBandForcing
method can be more relevant.
Example
Define a mutual friction forcing based on a large-scale normal fluid velocity field (see SyntheticFields.FourierBandVectorField
):
julia> using VortexPasta.Forcing: NormalFluidForcing
julia> using VortexPasta.SyntheticFields: SyntheticFields, FourierBandVectorField
julia> using Random: Xoshiro
julia> rng = Xoshiro(42); # initialise random number generator (optional, but recommended)
julia> Ls = (2π, 2π, 2π); # domain dimensions
julia> vn_rms = 1.0; # typical magnitude (rms value) of normal fluid velocity components
julia> vn = FourierBandVectorField(undef, Ls; kmin = 0.1, kmax = 1.5) # create field with non-zero Fourier wavevectors kmin ≤ |k⃗| ≤ kmax
FourierBandVectorField{Float64, 3} with 9 independent Fourier coefficients in |k⃗| ∈ [1.0, 1.4142]
julia> SyntheticFields.init_coefficients!(rng, vn, vn_rms); # randomly set non-zero Fourier coefficients of the velocity field
julia> forcing = NormalFluidForcing(vn; α = 0.8, α′ = 0)
NormalFluidForcing{Float64} with:
├─ Normal velocity field: FourierBandVectorField{Float64, 3} with 9 independent Fourier coefficients in |k⃗| ∈ [1.0, 1.4142]
└─ Friction coefficients: α = 0.8 and α′ = 0.0
VortexPasta.Forcing.FourierBandForcing
— TypeFourierBandForcing <: AbstractForcing
FourierBandForcing(vn::FourierBandVectorField; α, α′ = 0, filtered_vorticity = false)
Forcing due to mutual friction of a normal fluid with a Fourier-filtered superfluid velocity.
This forcing is similar to NormalFluidForcing
, but tries to only affect scales within a given band [kmin, kmax]
in Fourier space. This is achieved by a normal fluid velocity field represented by a FourierBandVectorField
, and by a modified Schwarz's equation in which only a coarse-grained superfluid flow is taken into account in the estimation of the mutual friction term.
Concretely, the vortex line velocity according to this forcing type is:
\[\frac{\mathrm{d}\bm{s}}{\mathrm{d}t} = \bm{v}_{\text{L}} = \bm{v}_{\text{s}} + \bm{v}_{\text{f}}\]
The forcing velocity is of the form:
\[\bm{v}_{\text{f}} = α \bm{s}' × \bm{v}_{\text{ns}}^{>} - α' \bm{s}' × \left( \bm{s}' × \bm{v}_{\text{ns}}^{>} \right)\]
where $\bm{v}_{\text{ns}}^{>} = \bm{v}_{\text{n}} - \bm{v}_{\text{s}}^{>}$ is a filtered slip velocity. In practice, the filtered velocity is active within the same wavenumber range [kmin, kmax]
where vn
is defined. See NormalFluidForcing
for other definitions.
Using a filtered vorticity field
To further ensure that this forcing only affects the chosen range of scales, one can pass filtered_vorticity = true
, which will replace the local unit tangent $\bm{s}'$ with a normalised coarse-grained vorticity. This corresponds to setting $\bm{s}' = \bm{ω}^{>} / |\bm{ω}^{>}|$ where $\bm{ω}^{>}$ is the Fourier-filtered vorticity field.
VortexPasta.Forcing.FourierBandForcingBS
— TypeFourierBandForcingBS <: AbstractForcing
FourierBandForcingBS(; kmin, kmax, α, ε_target)
Forcing based on Biot–Savart energetics within a given range of wavenumbers.
This type of forcing does not rely on an imposed normal fluid velocity. Instead, it starts from the functional derivative of the energy at a given wavevector $\bm{k}$ with respect to the vortex positions (according to the Biot–Savart law), and applies a velocity that ensures energy to increase (if $α > 0$ or $ε_{\text{target}} > 0$) at those wavevectors.
One should pass either α
or ε_target
but never both. They should be positive for energy injection (negative values lead to energy dissipation):
α
(\alpha
) is a non-dimensional coefficient which directly sets the amplitude of the forcing velocity;ε_target
(\varepsilon_target
) has the units of an energy injection rate. In this case, the amplitude $α$ will be adjusted over time in order to keep a roughly constant energy injection rate (which in general will not be equal toε_target
, see remarks below).
Extended help
Forcing definition
This forcing attempts to increase the kinetic energy at selected wavenumbers. Its definition starts from the expression for the kinetic energy at wavenumber $\bm{k}$:
\[E(\bm{k}) = \frac{1}{2} |\bm{v}(\bm{k})|^2 = \frac{1}{2k^2} |\bm{ω}(\bm{k})|^2\]
The idea is to translate the vortex positions by $\bm{s}(ξ) → \bm{s}(ξ) + δ\bm{s}(ξ)$ so that the energy $E(\bm{k})$ increases. To determine such a displacement, one can look at the functional derivative of $E(\bm{k})$ with respect to the positions $\bm{s}$:
\[\begin{align*} \frac{δE(\bm{k})}{δ\bm{s}} &= \frac{1}{2k^2} \bm{ω}(\bm{k}) ⋅ \frac{δ\bm{ω}^*(\bm{k})}{δ\bm{s}} + \text{c.c.}, \\ &= \frac{1}{2} \bm{ψ}(\bm{k}) ⋅ \frac{δ\bm{ω}^*(\bm{k})}{δ\bm{s}} + \text{c.c.}, \end{align*}\]
where $()^*$ denotes a complex conjugate and "c.c." means the complex conjugate of the first term. Here we have used $\bm{ω}(\bm{k}) = k^2 \bm{ψ}(\bm{k})$, which corresponds in physical space to $\bm{ω} = -∇² \bm{ψ}$.
The functional derivative of vorticity in Fourier space is:
\[\begin{align*} \frac{δ\bm{ω}^*(\bm{k})}{δ\bm{s}} &= \frac{Γ}{V} \frac{δ}{δ\bm{s}} ∮ e^{+i \bm{k} ⋅ \bm{s}(ξ)} \bm{s}(ξ)' \, \text{d}ξ \\ &= \frac{Γ}{V} \frac{δ}{δ\bm{s}} ∮ \bm{ℒ}[\bm{s}(ξ), \bm{s}(ξ)'] \, \text{d}ξ \\ &= \frac{Γ}{V} \left[ \frac{∂\bm{ℒ}}{∂\bm{s}} - \frac{\text{d}}{\text{d}ξ} \frac{∂\bm{ℒ}}{∂\bm{s}'} \right] \\ &= \frac{i Γ}{V} e^{+i \bm{k} ⋅ \bm{s}} \left[ \bm{k} ⊗ \bm{s}' - (\bm{k} ⋅ \bm{s}') I \right] = \frac{i Γ}{V} \bm{B}(\bm{k}, \bm{s}, \bm{s}') \end{align*}\]
where $\bm{B}$ is a $3×3$ complex matrix which has dimensions of an inverse length ($L^{-1}$).
The functional derivative of $E(\bm{k})$ can now be written as:
\[\begin{align*} \frac{δE(\bm{k})}{δ\bm{s}} &= \frac{Γ}{2V} i \bm{B} \bm{ψ}(\bm{k}) + \text{c.c.} \\ &= \frac{Γ}{2V} [\bm{s}' × \bm{v}(\bm{k})] e^{i \bm{k} ⋅ \bm{s}} + \text{c.c.} \\ &= ℜ \left\{ \frac{Γ}{V} [\bm{s}' × \bm{v}(\bm{k})] e^{i \bm{k} ⋅ \bm{s}} \right\} \end{align*}\]
Finally, the idea is to advect the filaments with a velocity which is parallel to this result for each forced $\bm{k}$. One can write this velocity as
\[\bm{v}_{\text{f}}(\bm{k}, \bm{s}) = α \, ℜ \left\{ [\bm{s}' × \bm{v}(\bm{k})] e^{i \bm{k} ⋅ \bm{s}} \right\} = α \, \bm{v}_0(\bm{k}, \bm{s})\]
where $α$ is a non-dimensional parameter setting the forcing amplitude.
Estimating the energy injection rate
One can also try to estimate an energy injection rate at wavevector $\bm{k}$ associated to this velocity:
\[\frac{\text{d}E(\bm{k})}{\text{d}t} = ∮ \frac{δE(\bm{k})}{δ\bm{s}} ⋅ \frac{\text{d}\bm{s}}{\text{d}t} \, \mathrm{d}ξ = α \frac{Γ}{V} ∮ |\bm{v}_0|^2 \, \mathrm{d}ξ\]
In general, this estimate may be quite inaccurate since the forcing can also affect the energy at wavevectors other than $\bm{k}$. But still, this estimate is the one used when $ε_{\text{target}}$ is given, and may allow to obtain a roughly constant energy injection rate (even if it's generally different than the "target" one).
Dissipation methods
VortexPasta.Forcing.NoDissipation
— TypeNoDissipation() <: AbstractDissipation
Repersents the absence of dissipation term.
VortexPasta.Forcing.SmallScaleDissipationBS
— TypeSmallScaleDissipationBS <: AbstractDissipation
SmallScaleDissipationBS(; kdiss, α, ε_target)
Dissipation based on Biot–Savart energetics at large wavenumbers (small scales).
This dissipation method works similarly to FourierBandForcingBS
. The main difference is that it acts at a different range of scales (wavenumbers k > kdiss
), and that positive values of α
or ε_target
lead to dissipation and not injection.
As in FourierBandForcingBS
, one should pass either α
or ε_target
but never both. They should be positive for energy dissipation (negative values lead to energy injection at small scales, which may lead to instabilities!):
α
(\alpha
) is a non-dimensional coefficient which directly sets the amplitude of the dissipation term;ε_target
(\varepsilon_target
) has the units of an energy dissipation rate. In this case, the amplitude $α$ will be adjusted over time in order to keep a roughly constant energy dissipation rate (which in general will not be equal toε_target
, as discussed inFourierBandForcingBS
).
Extended help
Dissipation term definition
The idea is to apply a "dissipation velocity" $\bm{v}_{\text{diss}}$ to the vortices which will extract energy from the small scales. Similarly to the forcing velocity in FourierBandForcingBS
, such dissipation velocity can be written as:
\[\bm{v}_{\text{diss}}(\bm{s}) = -α \bm{s}' × \bm{v}^{>}(\bm{s})\]
where $\bm{v}^{>}(\bm{s})$ denotes the high-pass filtered superfluid velocity (from Biot–Savart's law) at scales $k > k_{\text{diss}}$.
In practice, this velocity is obtained as $\bm{v}^{>} = \bm{v} - \bm{v}^{<}$, where $\bm{v}$ and $\bm{v}^{<}$ are the total and low-pass filtered superfluid velocities (up to scale $k_{\text{diss}}$ included). This means that the Fourier-space fields must be sufficiently well resolved, up to the chosen dissipative scale. Evaluating $\bm{v}^{<}$ at the vortex positions requires an extra type-2 NUFFT (interpolation from Fourier to physical space), which is a bit costly.
Energy dissipation rate
At scales $k > k_{\text{diss}}$, the dissipation rate associated to this term is:
\[ε_{\text{diss}} = -α \frac{Γ}{V} ∮ |\bm{s}' × \bm{v}^{>}|^2 \, \mathrm{d}ξ\]
Abstract types
VortexPasta.Forcing.AbstractForcing
— TypeAbstractForcing
Abstract type representing a forcing method.
Note that, in general, all forcing methods can also be used as dissipation methods (e.g. by setting forcing coefficients to a negative value).
VortexPasta.Forcing.AbstractDissipation
— TypeAbstractDissipation
Abstract type representing a dissipation method (via an extra dissipation term in the vortex velocity).
Internals
VortexPasta.Forcing.apply!
— FunctionForcing.apply!(forcing::AbstractForcing, vs::AbstractVector{<:Vec3}, f::AbstractFilament; [scheduler])
Apply forcing to a single filament f
with self-induced velocities vs
.
At output, the vs
vector is overwritten with the actual vortex line velocities.
The optional scheduler
keyword can be used to parallelise computations using one of the schedulers defined in OhMyThreads.jl.
Forcing.apply!(forcing::NormalFluidForcing, vs, vn, tangents; [scheduler])
This variant can be used in the case of a NormalFluidForcing
if one already has precomputed values of the normal fluid velocity and local unit tangents at filament points.