Filaments
VortexPasta.Filaments Module
FilamentsModule for dealing with the discretisation of curves in 3D space.
sourceTypes
VortexPasta.Filaments.AbstractFilament Type
AbstractFilament{T} <: AbstractVector{Vec3{T}}Abstract type representing a curve in 3D space.
The curve coordinates are parametrised as
The curve is discretised by a set of nodes (or discretisation points)
See ClosedFilament for a concrete implementation of AbstractFilament.
An AbstractFilament is treated as an AbstractVector of length N, in which each element is a discretisation point ClosedFilament for some examples.
Extended help
Evaluating coordinates and derivatives
Different ways are proposed of evaluating filament coordinates and derivatives along a filament f, depending on whether one wants values on discretisation points or in-between them.
In short, square brackets f[...] should be used to evaluate on filament nodes, while round brackets f(...) to evaluate in-between nodes.
Values on discretisation points
Coordinates
s⃗ = f[i]Derivatives at discretisation points can be similarly obtained by doing:
s⃗′ = f[i, Derivative(1)]
s⃗″ = f[i, Derivative(2)](Note that this also works with Derivative(0), in which case it's the same as f[i].)
For convenience, other geometric quantities can be evaluated in a similar way:
ρ⃗ = f[i, CurvatureVector()](See GeometricQuantity for available quantities.)
Values in-between discretisation points
In this case, one wants to evaluate a value in-between two discretisation points, i.e. for i. Two options are proposed:
if one knows the parametrisation of the filament (see
knotsto obtain the parametrisation knots), then one can evaluate using a value oft:s⃗ = f(t) s⃗′ = f(t, Derivative(1)) s⃗″ = f(t, Derivative(2))alternatively, if one knows the index
iassociated to the segment of interest, then one can dos⃗ = f(i, ζ) s⃗′ = f(i, ζ, Derivative(1)) s⃗″ = f(i, ζ, Derivative(2))where
ζmust be in, and the two limits correspond to knots and . This is convenient if one wants to evaluate, say, right in the middle between two discretisation points, in which case one would choose ζ = 0.5.
For convenience, other geometric quantities can be evaluated in a similar way:
ρ⃗ = f(t, CurvatureVector())
ρ⃗ = f(i, ζ, CurvatureVector())Derivatives
In all cases, derivatives are computed with respect to the parametrisation f(i, ζ, Derivative(1)), the derivative is not with respect to
Derivative normalisation
Since
One should use for example UnitTangent or CurvatureVector if one wants the derivatives with respect to the arc length normalise_derivatives which can be more efficient when one already has the derivatives with respect to
VortexPasta.Filaments.ClosedFilament Type
ClosedFilament{T, D <: DiscretisationMethod} <: AbstractFilament{T}Describes a closed curve (a loop) in 3D space.
It can also be used to represent infinite but unclosed curves described by a periodic function (such as an infinite straight line or a sinusoidal curve).
ClosedFilaments should be generally constructed using Filaments.init.
Extended help
Examples
The following examples use the CubicSplineMethod for representing filament curves, but other methods are also available.
Initialise filament from a set of discretisation points:
julia> f = Filaments.init(ClosedFilament, 16, CubicSplineMethod());
julia> θs = range(-1, 1; length = 17)[1:16]
-1.0:0.125:0.875
julia> @. f = Vec3(cospi(θs), sinpi(θs), 0);
julia> f[4]
3-element SVector{3, Float64} with indices SOneTo(3):
-0.3826834323650898
-0.9238795325112867
0.0
julia> f[5] = (f[4] + 2 * f[6]) ./ 2
3-element SVector{3, Float64} with indices SOneTo(3):
0.1913417161825449
-1.38581929876693
0.0
julia> update_coefficients!(f);Note that update_coefficients! should be called whenever filament coordinates are changed, before doing other operations such as estimating derivatives.
Estimate derivatives at discretisation points:
julia> f[4, Derivative(1)]
3-element SVector{3, Float64} with indices SOneTo(3):
0.9090457394297018
-0.7273334611006509
0.0
julia> f[4, Derivative(2)]
3-element SVector{3, Float64} with indices SOneTo(3):
0.20911715113294102
-2.09047051482799
0.0Estimate coordinates and derivatives in-between discretisation points:
julia> f(4, 0.32)
3-element SVector{3, Float64} with indices SOneTo(3):
-0.16753415613203387
-1.1324592487590195
0.0
julia> Ẋ, Ẍ = f(4, 0.32, Derivative(1)), f(4, 0.32, Derivative(2))
([0.8947546127964856, -0.9527970723463657, 0.0], [-0.3303413370703831, 0.17798009799460934, 0.0])
julia> X′, X″ = f(4, 0.32, UnitTangent()), f(4, 0.32, CurvatureVector())
([0.6845546705034081, -0.7289615237390588, 0.0], [-0.050762951240829336, -0.047670575508846375, 0.0])Curve parametrisation
The parametrisation knots
which is a zero-th order approximation (and a lower bound) for the actual arc length between points
VortexPasta.Filaments.Vec3 Type
Vec3{T}Three-element static vector, alias for SVector{3, T}.
Used to describe vectors and coordinates in 3D space.
sourceVortexPasta.Filaments.Derivative Type
Derivative{N}Represents the
Used in particular to interpolate derivatives along filaments.
sourceInitialisation
VortexPasta.Filaments.init Function
Filaments.init(
ClosedFilament{T}, N::Integer, method::DiscretisationMethod;
offset = zero(Vec3{T}),
parametrisation = Filaments.default_parametrisation(method),
nderivs = Val(2),
) -> ClosedFilament{T}Allocate data for a closed filament with N discretisation points.
The element type T can be omitted, in which case the default T = Float64 is used.
The optional offset keyword argument allows to define a filament with a spatial offset between points f[i] and f[i + N]. By default the offset is zero, meaning that the filament is a closed loop. This can be used for defining infinite (so not really closed) filaments living in periodic domains.
Possible discretisation methods include CubicSplineMethod, QuinticSplineMethod and FiniteDiffMethod.
Filaments.init(
ClosedFilament, points::AbstractVector{<:Vec3}, method::DiscretisationMethod;
kwargs...,
)Initialise new filament with the chosen discretisation points.
Note that update_coefficients! does not need to be called after using this variant (until node locations change, of course!).
Filaments.init(S::Function, ClosedFilament{T}, τs::AbstractVector, method::DiscretisationMethod)
Filaments.init(S::Function, ClosedFilament{T}, N::Integer, method::DiscretisationMethod)Construct new filament from parametric function
The parametric function SVector{3} or an NTuple{3}) for any
In particular, one can pass functions generated by PredefinedCurves.define_curve.
Two variants of this method are provided:
in the first variant, the function
is evaluated at user-provided values of , which should be in ; in the second variant, one simply passes the desired number
of nodes of the resulting filament, and the function chooses an equispaced list of values.
The element type T can be omitted, in which case the default T = Float64 is used.
Some examples are given further below ("Initialising filament from parametric function"). See also from_vector_field to initialise a filament from an analytical vector field.
Extended help
Evaluating high-order derivatives
By default the filament discretisation allows to evaluate up to the second derivative of the curve with respect to its parametrisation. This means in particular than one has access to the tangent, curvature and binormal vectors.
One may want to evaluate higher order derivatives, for example to also have access to the curve torsion (which depends on the 3rd derivative). This is only possible for some high-order discretisation methods (QuinticSplineMethod, as well as FourierMethod with the limitation that one has to evaluate on discretisation points). Moreover, one needs to explicitly pass nderivs = Val(3) (or higher) to the init function.
Customising the filament parametrisation
By default the filament is parametrised as
One can use the parametrisation keyword argument to change this. This may be a function parametrisation(Xs, i) which takes the vector Xs of discretisation points and the index i associated to the start of the current segment.
For instance, to parametrise filaments according to the
parametrisation = (Xs, i) -> abs(Xs[i + 1].z - Xs[i].z)One can also pass a predefined parametrisation. See AbstractParametrisation for a list of options.
Examples
Initialising filament from parametric function
Initialise circular filament with N = 16 nodes:
julia> S(t) = (cos(2π * t), sin(2π * t), 0) # define a circular ring with period T = 1
S (generic function with 1 method)
julia> N = 16;
julia> f = Filaments.init(S, ClosedFilament, N, CubicSplineMethod())
16-element ClosedFilament{SVector{3, Float64}, CubicSplineMethod}:
[0.9807852804032304, 0.19509032201612825, 0.0]
[0.8314696123025452, 0.5555702330196022, 0.0]
[0.5555702330196023, 0.8314696123025452, 0.0]
[0.19509032201612833, 0.9807852804032304, 0.0]
[-0.1950903220161282, 0.9807852804032304, 0.0]
[-0.555570233019602, 0.8314696123025453, 0.0]
[-0.8314696123025453, 0.5555702330196022, 0.0]
[-0.9807852804032304, 0.1950903220161286, 0.0]
[-0.9807852804032304, -0.19509032201612836, 0.0]
[-0.8314696123025455, -0.555570233019602, 0.0]
[-0.5555702330196022, -0.8314696123025452, 0.0]
[-0.19509032201612866, -0.9807852804032303, 0.0]
[0.1950903220161283, -0.9807852804032304, 0.0]
[0.5555702330196018, -0.8314696123025455, 0.0]
[0.8314696123025452, -0.5555702330196022, 0.0]
[0.9807852804032303, -0.19509032201612872, 0.0]Same but choosing the locations τ:
julia> τs = range(0, 1; length = N + 1)[1:N] # make sure the location τ = 1 is *not* included!
0.0:0.0625:0.9375
julia> f = Filaments.init(S, ClosedFilament, τs, CubicSplineMethod())
16-element ClosedFilament{SVector{3, Float64}, CubicSplineMethod}:
[1.0, 0.0, 0.0]
[0.9238795325112867, 0.3826834323650898, 0.0]
[0.7071067811865476, 0.7071067811865475, 0.0]
[0.38268343236508984, 0.9238795325112867, 0.0]
[6.123233995736766e-17, 1.0, 0.0]
[-0.3826834323650897, 0.9238795325112867, 0.0]
[-0.7071067811865475, 0.7071067811865476, 0.0]
[-0.9238795325112867, 0.3826834323650899, 0.0]
[-1.0, 1.2246467991473532e-16, 0.0]
[-0.9238795325112868, -0.38268343236508967, 0.0]
[-0.7071067811865477, -0.7071067811865475, 0.0]
[-0.38268343236509034, -0.9238795325112865, 0.0]
[-1.8369701987210297e-16, -1.0, 0.0]
[0.38268343236509, -0.9238795325112866, 0.0]
[0.7071067811865474, -0.7071067811865477, 0.0]
[0.9238795325112865, -0.3826834323650904, 0.0]Using VortexPasta.PredefinedCurves:
julia> using VortexPasta.PredefinedCurves
julia> trefoil = define_curve(TrefoilKnot());
julia> f = Filaments.init(trefoil, ClosedFilament, N, CubicSplineMethod())
16-element ClosedFilament{SVector{3, Float64}, CubicSplineMethod}:
[0.9604571867463079, -0.866973784619343, -0.5555702330196022]
[2.4033292980421757, 0.06610274757236567, -0.9807852804032304]
[2.679228677325119, 1.3209370977497819, -0.19509032201612828]
[1.74615214513341, 2.042849387038702, 0.8314696123025452]
[0.21541841567305087, 1.6526687430064453, 0.8314696123025452]
[-1.0162894527200281, 0.20979663171057739, -0.19509032201612828]
[-1.2921888320029713, -1.5968364770327248, -0.9807852804032304]
[-0.5702765427140513, -2.828544345425804, -0.5555702330196022]
[0.5702765427140513, -2.828544345425804, 0.5555702330196022]
[1.2921888320029713, -1.5968364770327248, 0.9807852804032304]
[1.0162894527200281, 0.20979663171057739, 0.19509032201612828]
[-0.21541841567305087, 1.6526687430064453, -0.8314696123025452]
[-1.74615214513341, 2.042849387038702, -0.8314696123025452]
[-2.679228677325119, 1.3209370977497819, 0.19509032201612828]
[-2.4033292980421757, 0.06610274757236567, 0.9807852804032304]
[-0.9604571867463079, -0.866973784619343, 0.5555702330196022]VortexPasta.Filaments.from_vector_field Function
Filaments.from_vector_field(
ClosedFilament, ωf::Function, s⃗₀, dτ, method::DiscretisationMethod;
max_steps = 1000, nsubsteps = 1, redistribute = true,
) -> ClosedFilamentInitialise closed filament from vector field.
Here ωf is a function x⃗ and returns a vector value ω⃗. A filament will be created such that, for each point
A possible application of this function is for constructing a filament which approximates vortex lines from a vorticity field ωf = Filaments.curl(vf) (see curl) where vf is a function defining the velocity field.
One may use Filaments.distance_to_field to verify the result of this function.
Positional arguments
ωf::Function: function taking a 3D coordinatex⃗and returning a vector valueω⃗;s⃗₀::Vec3: location where to start iterating. This point is guaranteed to be in the generated filament;dτ::Real: approximately distance between filament nodes. Determines the number of nodes in the resulting filament. The value ofdτ/nsubstepsshould be small enough to converge and so that the discretised lines are properly closed (see below for details);method::DiscretisationMethod: discretisation method to use (e.g.QuinticSplineMethod()).
Optional keyword arguments
max_steps::Int = 1000: maximum number of steps before we stop iterating. This is also the maximum possible length of the returned filament;nsubsteps::Int = 1: number of solver substeps to perform for each spatial incrementdτ. Larger values may be used to improve accuracy;periods = (Lx, Ly, Lz): domain dimensions in the case of a periodic domain. This may be used to close a curve if it crosses the domain one or more times. By default this isnothing, meaning that domain periodicity is not taken into account.redistribute = true: iftrue(default),redistribute_nodes!is called at the end to make sure that nodes are approximately distributed in a uniform way along the filament.
Extended help
Example
Generate a single filament aligned with the Taylor–Green vorticity field.
For convenience, we work with the Taylor–Green velocity field, and the vorticity is obtained via automatic differentiation (but we could directly work with the analytical vorticity instead).
julia> function taylor_green_velocity(x⃗::Vec3)
x, y, z = x⃗
Vec3(
cos(x) * sin(y) * cos(z),
-sin(x) * cos(y) * cos(z),
0,
)
end
taylor_green_velocity (generic function with 1 method)
julia> ωf = Filaments.curl(taylor_green_velocity) # vorticity field (via automatic differentiation)
#60 (generic function with 1 method)
julia> s⃗₀ = Vec3(0.1, 1.8, 0.42); # starting point for creating the filament
julia> dτ = 0.1; # pseudo time-step (has units of length; determines line resolution)
julia> nsubsteps = 4; # this is just to ensure convergence (should be larger for larger dτ)
julia> f = Filaments.from_vector_field(ClosedFilament, ωf, s⃗₀, dτ, QuinticSplineMethod(); nsubsteps);
julia> summary(f)
"29-element ClosedFilament{SVector{3, Float64}, QuinticSplineMethod}"
julia> Filaments.distance_to_field(ωf, f) # check that we're close to the actual vortex line
3.9561046185765664e-5Implementation details
The filament is generated by numerically solving the ODE:
where
In this context, the
Note that the curve will be automatically closed (and the ODE stopped) if we reach an max_steps solver iterations.
Curve representation
Discretisation methods
VortexPasta.Filaments.DiscretisationMethod Type
DiscretisationMethodAbstract type defining a filament discretisation method.
sourceVortexPasta.Filaments.SplineMethod Type
SplineMethod{k} <: DiscretisationMethodRepresents parametric curves using splines of order
A spline of order
See also CubicSplineMethod and QuinticSplineMethod.
VortexPasta.Filaments.CubicSplineMethod Type
CubicSplineMethod <: DiscretisationMethodRepresents curves using cubic splines.
This is an alias for SplineMethod{4} (see SplineMethod).
In the case of closed curves, periodic cubic splines are used.
sourceVortexPasta.Filaments.QuinticSplineMethod Type
QuinticSplineMethod <: DiscretisationMethodRepresents curves using quintic splines.
This is an alias for SplineMethod{6} (see SplineMethod).
A quintic spline is made of polynomials of degree 5 and has global continuity
In the case of closed curves, periodic quintic splines are used.
sourceVortexPasta.Filaments.FiniteDiffMethod Type
FiniteDiffMethod{M} <: DiscretisationMethod{M}
FiniteDiffMethod([M = 2], [interpolation = HermiteInterpolation(M)])Estimation of curve derivatives at filament nodes using finite differences.
For now, only the case M = 2 (default) is implemented (4th order / 5-point finite differences), following the method used by Baggaley and Barenghi (2011) based on a paper by Gamet et al. (1999).
FiniteDiffMethod also requires specifying an interpolation scheme for evaluating coordinates and derivatives in-between discretisation points. By default, Hermite interpolations of continuity M = 2, that means quintic Hermite interpolations, which match the first two derivatives estimated by finite differences at the discretisation points.
References
(Baggaley and Barenghi, 2011) Baggaley & Barenghi, Phys. Rev. B 83, 134509 (2011)
(Gamet et al., 1999) Gamet et al., Int. J. Numer. Meth. Fluids 29, 2 (1999)
VortexPasta.Filaments.FourierMethod Type
FourierMethod <: DiscretisationMethod
FourierMethod(interpolation = HermiteInterpolation(2))Represents closed curves using Fourier series.
Derivatives at nodes are estimated using FFTs. To interpolate in-between nodes, a local interpolation method (typically HermiteInterpolation) is used, which is much faster (but less accurate) than evaluating Fourier series.
Note that using FFTs require the knot locations
This method should only be used for simple settings and for verification of filament derivatives in other methods.
sourceVortexPasta.Filaments.discretisation_method Function
discretisation_method(f::AbstractFilament) -> DiscretisationMethodReturn the method used to discretise the filament based on its node locations.
sourceInterpolation
VortexPasta.Filaments.interpolation_method Function
interpolation_method(m::DiscretisationMethod)Return the interpolation method associated to a DiscretisationMethod.
For FiniteDiffMethod and FourierMethod, this is usually a HermiteInterpolation (which requires derivatives at the interpolation nodes). For SplineMethods, since splines don't rely on a separate interpolation scheme, this simply returns m (i.e. the passed SplineMethod).
VortexPasta.Filaments.required_derivatives Function
required_derivatives(m::DiscretisationMethod) -> IntReturn the number of derivatives on interpolation nodes required by the discretisation method.
This is generally larger than 0 for methods relying on Hermite interpolations (which require derivatives on interpolation nodes). This is 0 for other methods such as SplineMethod.
VortexPasta.Filaments.init_coefficients Function
init_coefficients(method::DiscretisationMethod, ys::AbstractVector, [nderivs::Val]) -> DiscretisationCoefs
init_coefficients(method::DiscretisationMethod, cs::PaddedVector, cderivs::NTuple) -> DiscretisationCoefsInitialise interpolation coefficients.
In the first case, the ys vector is interpreted as a vector of values at interpolation nodes, and it will not be modified by the function. Note that this requires the allocation of a coefficient vector. Moreover, the nderivs argument indicates the number of derivatives that one wants to be able to compute. By default, nderiv is chosen as the minimum number of derivatives required by the method (see required_derivatives).
In the second case, cs is interpreted as a vector of interpolation coefficients, and is thus stored in the returned structure. Moreover, to compute one or more derivatives, one can pass cderivs vectors which should have the same type and length as cs.
Hermite interpolations
Some discretisation methods (FiniteDiffMethod, FourierMethod) require one or more derivatives to evaluate Hermite interpolations, as determined by required_derivatives.
In the first variant, if one passes e.g. nderivs = Val(0), then this argument will be silently replaced by the required number of derivatives to make things work.
In the second variant, things will fail if length(cderivs) < required_derivatives(method).
VortexPasta.Filaments.compute_coefficients! Function
compute_coefficients!(coefs::DiscretisationCoefs, [ys::AbstractVector], ts::PaddedVector)Compute interpolation coefficients of parametric function
Here ys contains values at interpolation nodes, and ts contains the values of the curve parameter
If ys is not passed, then the coefs.cs vector is expected to have the values at interpolation points (which will be overwritten with the interpolation coefficients).
This should be called before evaluating an interpolation using evaluate.
VortexPasta.Filaments.evaluate Function
evaluate(coefs::DiscretisationCoefs, ts::PaddedVector, t::Real, [Derivative(0)]; [ileft = nothing])
evaluate(coefs::DiscretisationCoefs, ts::PaddedVector, i::Int, ζ::Real, [Derivative(0)])Evaluate interpolation at a given location
In the first case, i (such that ts[i] ≤ t < ts[i + 1]) using the ileft keyword argument.
In the second case, i.
VortexPasta.Filaments.LocalInterpolationMethod Type
LocalInterpolationMethodAbstract type defining a local interpolation method for estimating curve properties (coordinates, derivatives) in-between interpolation points using local information.
sourceVortexPasta.Filaments.HermiteInterpolation Type
HermiteInterpolation{M} <: LocalInterpolationMethodHermite interpolation of continuity
Hermite interpolations are obtained using curve derivatives up to order
Allowed cases
for
this is simply linear interpolation (note that curvatures cannot be estimated from linear interpolations); for
this is the standard Hermite interpolation (piecewise cubic polynomials, requires first derivatives at interpolation points); for
this is a quintic Hermite interpolation requiring first and second derivatives at interpolation points.
VortexPasta.Filaments.interpolate Function
interpolate(
method::LocalInterpolationMethod, derivative::Derivative, t::Number,
values_at_interpolation_points...,
)Interpolate coordinates
Under this form, the location
The values_at_interpolation_points depend on the interpolation method:
- for
HermiteInterpolation, one should pass the coordinatesand the first derivatives at the two endpoints of the interval ( and ).
Obtaining information
VortexPasta.Filaments.knots Function
knots(f::AbstractFilament{T}) -> AbstractVector{T}Return parametrisation knots
Filaments are parametrised by
VortexPasta.Filaments.knotlims Function
knotlims(f::AbstractFilament) -> (t_begin, t_end)Return limits within which the filament can be evaluated.
sourceVortexPasta.Filaments.end_to_end_offset Function
end_to_end_offset(f::ClosedFilament{T}) -> Vec3{T}Return the end-to-end offset Δ⃗ = f[end + 1] - f[begin] of a "closed" filament.
For actually closed filaments, the end-to-end offset is zero. However, ClosedFilament also supports the case of infinite (but unclosed) filaments, which infinitely extend along one or more Cartesian directions. The restriction imposed by ClosedFilament is that infinite filaments repeat themselves periodically, such that f[i + m * N] == f[i] + m * Δ⃗ where N is the length of the filament (i.e. the number of degrees of freedom, or the total number of independent filament nodes).
Base.minimum Function
Base.minimum(node_distance, f::AbstractFilament) -> Real
Base.minimum(node_distance, fs::AbstractVector{<:AbstractFilament}) -> RealReturn the minimum distance
If a vector of filaments is passed (variant 2), then the minimum among all filaments is returned.
sourceBase.minimum(knot_increment, f::AbstractFilament) -> Real
Base.minimum(knot_increment, fs::AbstractVector{<:AbstractFilament}) -> RealReturn the minimum increment
This is generally a good approximation for the minimum segment length, at least when the default ChordalParametrisation is used. If this is not the case, it is better to pass node_distance instead of knot_increment, which always returns the distance between two neighbouring nodes.
Base.maximum Function
Base.maximum(node_distance, f::AbstractFilament) -> Real
Base.maximum(node_distance, fs::AbstractVector{<:AbstractFilament}) -> RealReturn the maximum distance
If a vector of filaments is passed (variant 2), then the maximum among all filaments is returned.
sourceBase.maximum(knot_increment, f::AbstractFilament) -> Real
Base.maximum(knot_increment, fs::AbstractVector{<:AbstractFilament}) -> RealReturn the maximum increment
This is generally a good approximation for the maximum segment length, at least when the default ChordalParametrisation is used. If this is not the case, it is better to pass node_distance instead of knot_increment, which always returns the distance between two neighbouring nodes.
VortexPasta.Filaments.minimum_node_distance Function
minimum_node_distance(f::AbstractFilament) -> Real
minimum_node_distance(fs::AbstractVector{<:AbstractFilament}) -> RealReturn the minimum distance
This is equivalent to calling minimum(node_distance, f).
See also minimum and minimum_knot_increment.
VortexPasta.Filaments.minimum_knot_increment Function
minimum_knot_increment(f::AbstractFilament) -> Real
minimum_knot_increment(fs::AbstractVector{<:AbstractFilament}) -> RealReturn the minimum increment
This is equivalent to calling minimum(knot_increment, f).
This is generally a good and fast approximation for the minimum segment length. However, this approximation is generally incorrect if one is using the FourierMethod discretisation method.
See also minimum, minimum_node_distance and maximum_knot_increment.
VortexPasta.Filaments.maximum_knot_increment Function
maximum_knot_increment(f::AbstractFilament) -> Real
maximum_knot_increment(fs::AbstractVector{<:AbstractFilament}) -> RealReturn the maximum increment
This is equivalent to calling maximum(knot_increment, f).
This is generally a good approximation for the maximum segment length. See also maximum and minimum_knot_increment.
VortexPasta.Filaments.nodes Function
nodes(f::AbstractFilament{T}) -> AbstractVector{T}Return the nodes (or discretisation points)
VortexPasta.Filaments.filament_length Function
filament_length(f::AbstractFilament; quad = nothing) -> Real
filament_length(fs::AbstractVector{<:AbstractFilament}; quad = nothing) -> RealEstimate the length of one or more filaments.
By default, the filament length is estimated using a straight segment approximation, which is fast but doesn't account for the actual curve geometry in-between discretisation points, underestimating the actual length. A quadrature rule may be optionally passed using quad (e.g. quad = GaussLegendre(4)) to obtain a more accurate result.
See also segment_length, which is used by this function.
Requirements
In the straight-segment implementation (quad = nothing), one needs the filaments to be closed, i.e. f[end + 1] should be set to the right value. This can be achieved either by update_coefficients! or close_filament!.
In the quadrature-based implementation (quad <: AbstractQuadrature), the interpolation coefficients must already have been computed via update_coefficients!.
VortexPasta.Filaments.distance_to_field Function
Filaments.distance_to_field(ωf::Function, f::AbstractFilament) -> RealReturn an estimate of the normalised "distance" between a filament and a target vector field.
This function is meant to be used to verify the result of Filaments.from_vector_field, more specifically to verify that the filament is everywhere tangent to the objective vector field.
Returns 0 if the filament is perfectly tangent to the vector field at all discretisation points.
See Filaments.from_vector_field for more details.
VortexPasta.Filaments.minimum_nodes Function
Filaments.minimum_nodes(method::DiscretisationMethod) -> Int
Filaments.minimum_nodes(f::AbstractFilament) -> IntReturn the minimum number of filament nodes needed by the method.
This is usually equal or larger than 3, depending on the method.
sourceModifying filaments
VortexPasta.Filaments.update_coefficients! Function
update_coefficients!(f::AbstractFilament; knots = nothing)Compute coefficients needed to perform inter-node interpolations and estimate derivatives.
Uses the current locations of the filament nodes. If nodes change, this function should be called to update the coefficients.
By default, this function also updates the parametrisation knots knots vector as a keyword argument. In particular, one can pass knots = knots(f) to keep the parametrisation knots unchanged.
This function will fail if the number of filament nodes is smaller than that required by the discretisation method. For instance, closed filaments discretised using cubic splines must have at least 3 nodes.
sourceVortexPasta.Filaments.close_filament! Function
close_filament!(f::ClosedFilament) -> fSet the filamend endpoint based on its starting point and its end-to-end offset.
This sets f[end + 1] = f[begin] + Δ⃗ where Δ⃗ is the end-to-end offset of the filament (see end_to_end_offset).
Calling this function is not needed if one has recently used update_coefficients!, which already closes the filaments. This function should be used as a cheaper alternative to update_coefficients!, when one only wants to close a filament (for instance to compute its length via filament_length). This function can also be used when the filament is no longer considered as "valid" by the chosen discretisation method, which happens when the number of nodes falls below some threshold (see check_nodes).
VortexPasta.Filaments.change_offset Function
change_offset(f::ClosedFilament, offset::Vec3{<:Real}) -> f′Change spatial offset between a filament start and endpoints.
See init for more details on optional spatial offsets.
This function is allocation-free. It returns a new filament which shares the same arrays as f, and only differs in the offset. Modifying nodes of the returned filament also modifies nodes of f.
VortexPasta.Filaments.fold_periodic! Function
fold_periodic!(Xs::AbstractVector{<:Vec3}, Ls::NTuple{3, Real}) -> BoolFold a set of coordinates onto the main unit cell.
The idea is that the filament coordinates are (mainly) in the main unit cell, given by
This is nice for visualisations, and might also improve performance of the folding required by long-range computations.
To avoid creating discontinuities in the filament, what this function actually does is to make sure that the average among all filament nodes is in the main unit cell.
Returns true if coordinates were modified, false otherwise. In the first case, one may want to call update_coefficients! to update the curve representation.
fold_periodic!(f::AbstractFilament, Ls) -> BoolFold filament nodes onto the main unit cell.
Returns true if coordinates were modified, false otherwise.
If true, coefficients may need to be updated afterwards using update_coefficients!.
VortexPasta.Filaments.redistribute_nodes! Function
redistribute_nodes!(f::AbstractFilament) -> fRedistribute nodes of the filament so that they are (approximately) equally spaced.
More precisely, this function repositions the filament nodes such that the knot spacing f[i] = f((i - 1) * Δt) where
One does not need to call update_coefficients! after calling this function.
VortexPasta.Filaments.split! Function
split!(f::ClosedFilament, i::Int, j::Int; p⃗ = Vec3(0, 0, 0)) -> (f₁, f₂)Split closed filament into two filaments.
Assuming j > i, the resulting filaments are respectively composed of nodes f[i + 1:j] and f[(j + 1:end) ∪ (begin:i)].
In practice, a split makes sense when the nodes f[i] and f[j] - p⃗ are spatially "close". Here p⃗ is an optional offset which usually takes into account domain periodicity (see also find_min_distance).
Note that this function modifies the filament f, which should then be discarded.
One should generally call update_coefficients! on both filaments after a split.
VortexPasta.Filaments.merge! Function
merge!(f::ClosedFilament, g::ClosedFilament, i::Int, j::Int; p⃗ = Vec3(0, 0, 0)) -> ClosedFilamentMerge two closed filaments into one.
The resulting filament is composed of nodes:
f[begin:i] ∪ {g[(j + 1:end) ∪ (begin:j)] - p⃗} ∪ f[i + 1:end]Here p⃗ is an optional offset which usually takes into account domain periodicity (see also find_min_distance).
This function returns a new filament h which may share memory with f. The filament g is not modified.
One should generally call update_coefficients! on the returned filament after merging.
Merging a filament with its shifted image
This function supports merging a filament f with a shifted version of itself, g = f + p⃗. For this, one should simply pass g = f and a non-zero offset p⃗.
Curve parametrisations
VortexPasta.Filaments.AbstractParametrisation Type
AbstractParametrisationAbstract type representing a curve parametrisation.
For a parametric curve
Some predefined parametrisations include:
Moreover, CustomParametrisation allows to define custom parametrisation functions.
VortexPasta.Filaments.ChordalParametrisation Type
ChordalParametrisation <: AbstractParametrisation
ChordalParametrisation()Represents the chordal parametrisation.
In this case, the increment between two knots is roughly equal to the length of the segment between the knots:
This is known as the chordal parametrisation in the context of Catmull–Rom splines.
This is the recommended (and default) parametrisation for general cases.
sourceVortexPasta.Filaments.CentripetalParametrisation Type
CentripetalParametrisation <: AbstractParametrisation
CentripetalParametrisation()Represents the centripetal parametrisation.
In this case, the increment between two knots is given by:
This is known as the centripetal parametrisation in the context of Catmull–Rom splines.
sourceVortexPasta.Filaments.FourierParametrisation Type
FourierParametrisation <: AbstractParametrisation
FourierParametrisation()Curve parametrisation adapted for Fourier series representation of curves.
In this case, the increment between two knots is constant and given by:
where
VortexPasta.Filaments.CustomParametrisation Type
CustomParametrisation{F <: Function} <: AbstractParametrisation
CustomParametrisation(f::Function)Allows to define a custom curve parametrisation.
Here the function f should have the signature f(Xs, i) → Δt where Xs is the vector of discretisation nodes. It must return the knot increment Δt = t_{i + 1} - t_{i}.
For instance, the ChordalParametrisation corresponds to defining
f(Xs, i) = norm(Xs[i + 1] - Xs[i])Other functions
Base.getindex Method
Base.getindex(f::AbstractFilament{T}, i::Int) -> Vec3{T}
Base.getindex(f::AbstractFilament{T}, i::Int, ::Derivative{n}) -> Vec3{T}
Base.getindex(f::AbstractFilament{T}, i::Int, ::GeometricQuantity)Return coordinates of discretisation point
One may also obtain derivatives and other geometric quantities at point Derivative or GeometricQuantity.
Base.setindex! Method
Base.setindex!(f::AbstractFilament{T}, v, i::Int) -> Vec3{T}Set coordinates of discretisation point
VortexPasta.Filaments.normalise_derivatives Function
normalise_derivatives(ṡ::Vec3, s̈::Vec3) -> (s⃗′, s⃗″)
normalise_derivatives((ṡ, s̈)::NTuple) -> (s⃗′, s⃗″)Return derivatives with respect to the arc length
The returned derivatives satisfy:
is the unit tangent vector; is the curvature vector, where is the normal unit vector (with ) and is the curvature (and R the curvature radius).
VortexPasta.Filaments.normalise_derivatives! Function
normalise_derivatives!(Ẋ::AbstractVector, Ẍ::AbstractVector)Normalise vectors containing derivatives at filament locations.
If possible, prefer using normalise_derivatives, which works on a single filament location at a time.
See normalise_derivatives for more details.
VortexPasta.Filaments.integrate Function
integrate(integrand::Function, f::AbstractFilament, i::Int, quad::AbstractQuadrature; limits = nothing)
integrate(integrand::Function, s::Segment, quad::AbstractQuadrature; limits = nothing)Estimate integral along a filament segment using the chosen quadrature.
Integration is performed in the segment (f[i], f[i + 1]).
The function integrand(ζ) takes the arguments f, i and ζ corresponds to the position of a point within the segment. See further below for some examples.
By default, the integration is performed along the whole segment, that is, for the range limits = (a, b).
Examples
Estimate arc length of segment
quad = GaussLegendre(4) # quadrature rule
ℓ = integrate(f, i, quad) do f, i, ζ
norm(f(i, ζ, Derivative(1))) # = |∂ₜ𝐗|
endAlternatively:
quad = GaussLegendre(4) # quadrature rule
s = Segment(f, i)
ℓ = integrate(s, quad) do s, ζ
norm(s(ζ, Derivative(1))) # = |∂ₜ𝐗|
endintegrate(integrand::Function, f::AbstractFilament, quad::AbstractQuadrature; limits = nothing)Estimate integral over a whole filament.
The integral is computed as the sum of the integrals over each filament segment. The given quadrature rule is applied over each segment, meaning that the total number of evaluations is given by the number of segments multiplied by the length of the quadrature rule.
The signature of the integrated function must be integrand(f::AbstractFilament, i::Int, ζ::Real), where i is the index of the segment of interest. See below for some examples.
Examples
Estimate the total length of a closed filament,
quad = GaussLegendre(4) # quadrature rule
integrand(f, i, ζ) = norm(f(i, ζ, Derivative(1))) # = |∂ₜ𝐗|
# Here `f` is an existent filament.
L = integrate(integrand, f, quad)
# Or, more conveniently, using a `do` block to define an anonymous function.
L = integrate(f, quad) do ff, i, ζ
norm(ff(i, ζ, Derivative(1))) # = |∂ₜ𝐗|
endVortexPasta.Filaments.find_min_distance Function
find_min_distance(
fx::AbstractFilament, fy::AbstractFilament, i::Int, j::Int;
periods::NTuple{3, Real} = (Infinity(), Infinity(), Infinity()),
maxiter = 4, rtol = 1e-2,
)Determine the minimum distance between filament segments.
This function estimates the minimum distance between filament segments fx[i:i+1] and fy[j:j+1] via an iterative (Newton–Raphson) method.
The filaments fx and fy may be the same filament.
Returns
Returns a NamedTuple with the following fields:
ζx,ζy: optimal locations inwithin each segment; x⃗,y⃗: optimal locations within each segment;p⃗: periodic offset (each component is a multiple of the domain period along that direction);d⃗: minimum distance vector,d⃗ = x⃗ - y⃗ + p⃗.
Optional keyword arguments
periods: the period of the spatial domain. This should be given if one wants to take into account periodic images of the filaments;maxiter = 4: maximum number of iterations of the Newton method;rtol = 1e-2: relative tolerance for stopping the iterations. By default it's not very small since in general we don't need to be very precise.
VortexPasta.Filaments.check_nodes Function
check_nodes(Bool, f::AbstractFilament) -> Bool
check_nodes(f::AbstractFilament)Check whether current filament nodes are compatible with the filament discretisation method.
In its first form, this function returns false in case of incompatibility, while it throws an error in its second form.
For now, the only requirement is that the number of nodes must be larger than some small value. In particular, one can't have a closed filament with less than 3 nodes (but the specific discretisation method might impose some other small value).
See also minimum_nodes.
VortexPasta.Filaments.number_type Function
number_type(x) -> Type{<:Number}Obtain the number type associated to a container x.
This is expected to return a concrete type T <: Number.
This function can be useful when the actual number type is hidden behind many nested array types.
Some examples:
if
xis a singleAbstractFilament{T}, this returnsT;if
xis a vector or tuple ofAbstractFilament{T}, this also returnsT;if
xis a vector of vectors ofSVector{3, T}, this returnsT.
Note that, in the last two cases, this corresponds to eltype(eltype(eltype(x))), which is less readable and prone to errors.
VortexPasta.Filaments.curl Function
Filaments.curl(vf::Function) -> Function
Filaments.curl(vf::Function, x⃗::Vec3) -> Vec3Return curl of a vector-valued field,
Here vf is a function x⃗ and returns a vector value v⃗.
The curl of vf is obtained via automatic differentiation using ForwardDiff.jl.
The first variant returns a function which can be then evaluated at any x⃗. The second variant directly evaluates the curl at some x⃗.
Refinement
VortexPasta.Filaments.RefinementCriterion Type
RefinementCriterionAbstract type describing a curve refinement criterion.
Implemented refinement criteria are:
NoRefinement: disables refinement;RefineBasedOnSegmentLength: enforces a minimum and maximum distance between neighbouring filament nodes;RefineBasedOnCurvature: inserts more nodes on highly-curved filament segments.
VortexPasta.Filaments.NoRefinement Type
NoRefinement <: RefinementCriterion
NoRefinement()Used to disable filament refinement.
sourceVortexPasta.Filaments.RefineBasedOnSegmentLength Type
RefineBasedOnSegmentLength <: RefinementCriterion
RefineBasedOnSegmentLength(ℓ_min, ℓ_max = 2 * ℓ_min; ρℓ_max = Inf)Refinement criterion imposing a minimum segment length.
This refinement criterion imposes neighbouring filament nodes to be at a distance
nodes are inserted if the distance between two nodes is
. The insertion is done at an intermediate position using the functional representation of the filament (e.g. splines or Hermite interpolation); nodes are removed if the distance between two nodes is
. For a filament which is strongly curved at that point, this means that local information is lost and that the filament is smoothed.
The optional argument ρℓ_max sets the curvature limit so that higher curvature regions will be smoothed and total line length is decreased:
- nodes are removed if the local normalised curvature is
.
VortexPasta.Filaments.RefineBasedOnCurvature Type
RefineBasedOnCurvature <: RefinementCriterion
RefineBasedOnCurvature(ρℓ_max::Real, ρℓ_min = ρℓ_max / 2.5; ℓ_min = 0.0, ℓ_max = Inf)Curvature-based refinement criterion.
Node insertion
According to this criterion, a filament is locally refined if:
where
For splines, this uses a classical knot insertion algorithm which preserves the shape of the curve.
Node removal
Similarly, filaments nodes are removed based on the value of ρℓ_min. This value should be less than ρℓ_max / 2 to avoid alternatively adding and removing nodes when repeatedly calling refine!.
For safety, two adjacent nodes will never be removed in a single call to refine!.
Note that, when filaments are nearly straight, this may lead to the removal of most nodes. To limit this, set the keyword argument ℓ_max to some finite value determining the maximum length of a segment. Similarly, the keyword argument ℓ_min sets a lower limit for the distance between neighbouring nodes.
VortexPasta.Filaments.refine! Function
refine!(f::AbstractFilament, crit::RefinementCriterion) -> (Int, Int)Refine the filament according to a given criterion.
More precisely, this function can add and remove discretisation points according to the chosen refinement criterion.
Returns the number of added and removed nodes.
Example usage:
crit = RefineBasedOnCurvature(0.5)
refine!(f, crit)VortexPasta.Filaments.insert_node! Function
insert_node!(f::AbstractFilament, i::Integer, [ζ = 0.5]) -> Vec3Insert node in-between locations f[i] and f[i + 1].
The optional argument ζ ∈ [0, 1] corresponds to the relative location of the new node within the segment. By default it is set to ζ = 0.5, which corresponds to an estimation of the middle of the segment.
Note that update_after_changing_nodes! must be called after inserting one or more nodes.
See also remove_node!.
Returns the inserted node.
sourceVortexPasta.Filaments.remove_node! Function
remove_node!(f::AbstractFilament, i::Integer) -> Vec3Remove node at location f[i].
Note that update_after_changing_nodes! must be called after removing one or more nodes.
See also insert_node!.
Returns the removed node.
sourceVortexPasta.Filaments.update_after_changing_nodes! Function
update_after_changing_nodes!(f::AbstractFilament)Update filament fields after changing nodes.
Depending on the filament discretisation method, this can recompute derivatives, knots or discretisation coefficients.
Should be called after inserting or removing filament nodes. See insert_node! and remove_node!.
Geometric quantities
The following types are provided as a convenient way of evaluating scalar and vector quantities of interest along filaments.
VortexPasta.Filaments.GeometricQuantity Type
GeometricQuantityAbstract type defining a geometric quantity defined for a filament.
Some available geometric quantities include:
Evaluating geometric quantities works in the same way as evaluating derivatives.
sourceVortexPasta.Filaments.UnitTangent Type
UnitTangent <: GeometricQuantityRepresents the unit tangent vector
In terms of an arbitrary parametrisation
where derivatives are with respect to the parameter
VortexPasta.Filaments.CurvatureVector Type
CurvatureVector <: GeometricQuantityRepresents the curvature vector associated to a filament.
In terms of an arbitrary parametrisation
where derivatives are with respect to the parameter
The curvature vector can be written as
VortexPasta.Filaments.CurvatureScalar Type
CurvatureScalar <: GeometricQuantityRepresents the scalar curvature associated to a filament.
This is simply the norm of CurvatureVector and of CurvatureBinormal. It is explicitly given by
where derivatives are with respect to the arbitrary parametrisation
VortexPasta.Filaments.CurvatureBinormal Type
CurvatureBinormal <: GeometricQuantityRepresents the scaled binormal vector associated to a filament.
The scaled binormal vector is defined as
It is explicitly given by
where derivatives are with respect to the arbitrary parametrisation
VortexPasta.Filaments.TorsionScalar Type
TorsionScalar <: GeometricQuantityTorsion of a filament.
The torsion
It can be obtained as
where derivatives are with respect to an arbitrary curve parametrisation.
Note
Because it is obtained from third derivatives, estimating the torsion requires a high-order filament discretisation scheme such as QuinticSplineMethod.
Segments
Segment iterators
VortexPasta.Filaments.SegmentIterator Type
SegmentIterator{Filament <: AbstractFilament}Convenience type allowing to iterate over the segments of a filament.
sourceVortexPasta.Filaments.segments Function
segments(f::AbstractFilament) -> SegmentIterator(f)Create a SegmentIterator object for iterating over the segments of a filament.
Base.length Method
Base.length(s::SegmentIterator{<:AbstractFilament}) -> IntReturn the number of segments in a filament.
sourceBase.eachindex Method
Base.eachindex(s::SegmentIterator{<:AbstractFilament}) -> AbstractRangeReturn the indices associated to the segments of a filament.
sourceSingle segments
VortexPasta.Filaments.Segment Type
Segment{<:Filament}
Segment(f::AbstractFilament, i::Integer)Represents a single filament segment.
The segment goes from nodes f[i] to f[i + 1].
VortexPasta.Filaments.midpoint Function
midpoint(s::Segment) -> Vec3Return an estimation of the segment midpoint (prioritising performance over accuracy).
sourceVortexPasta.Filaments.segment_length Function
Filaments.segment_length(s::Segment; quad = nothing)Estimate length of a filament segment.
One may pass a quadrature rule as quad for better accuracy. Otherwise, if quad = nothing, this simply returns the straight distance between the two segment extremities.
Plotting
VortexPasta.Filaments.filamentplot! Function
filamentplot!([ax,] f::AbstractFilament, [velocities]; kws...)
MakieCore.plot!(ax, f::AbstractFilament, [velocities]; kws...)Plot filament onto existent 3D axis.
The first argument should typically be an Axis3 or an LScene.
Example usage:
using GLMakie
fig = Figure()
ax = Axis3(fig[1, 1])
plot!(ax, f) # f is a filamentOptional arguments and their defaults
refinement::Int = 1: level of refinement of the curves (must be ≥ 1)periods = (nothing, nothing, nothing). This can be a tuple of values representing the domain period, for instance(2π, 2π, 2π). In that case, filaments will be "broken" when they exit the main unit cell, so that all vortex elements are within the cell.color = :blacklinewidth = 1.5f0linestyle = :solidmarkercolor = nothing(nothing→ same ascolor)marker = :circlemarkersize = 10.0f0colormap = :viridiscolorrange = MakieCore.Automatic()
Arrow properties
One can pass an arrows3d named tuple to set arrow properties. For example:
arrows3d = (; shaftlength = 0.6, lengthscale = 2.0,)
See the Makie docs for a full list of available options.
Plotting tangent and curvature vectors
Tangent and curvature vectors can be optionally plotted via the tangents and curvatures arguments. A single vector will be plotted for each filament segments. By default, vectors are evaluated at filament nodes, but one can also evaluate them in-between nodes using the vectorpos argument.
tangents::Bool = false: plot unit tangent vectors.curvatures::Bool = false: plot curvature vectors. Note that the magnitude is the local curvature, where is the curvature radius. tangentcolor = nothingcurvaturecolor = nothingvectorpos = 0.0: relative vector positions within each segment. Must be in.
Plotting velocities of filament nodes
Similarly, it is possible to plot vector quantities attached to filament nodes, such as filament velocities. For this pass a vector of velocities as a positional argument after the filament f.
Associated keyword arguments:
velocitycolor = nothingcolour of velocity vectors
VortexPasta.Filaments.filamentplot Function
filamentplot(f::AbstractFilament, [velocities]; kws...)
MakieCore.plot(f::AbstractFilament, [velocities]; kws...)Plot a filament using Makie.jl.
Example usage:
using GLMakie
plot(f; refinement = 4) # f is a filamentSee filamentplot! for details and for optional keyword arguments.
Except for
FourierMethod, which requires the parametrisation to have a constant increment. ↩︎