LCOV - code coverage report
Current view: top level - src - ftle.jl (source / functions) Hit Total Coverage
Test: on branch nothing Lines: 99 105 94.3 %
Date: 2025-02-26 15:13:49 Functions: 0 0 -

          Line data    Source code
       1             : # TODO: migrate from FLoops to OhMyThreads?!
       2             : 
       3        3584 : @inline function _diff(F, h, i::Tuple, ::Val{K}) where {K}
       4        3584 :     (i[K] == 1) && return _diffl(F, h, i, Val(K))
       5        2944 :     (i[K] == size(F, K)) && return _diffr(F, h, i, Val(K))
       6             : 
       7        2304 :     ir = MVector(i...)
       8        2304 :     ir[K] += 1
       9        2304 :     il = MVector(i...)
      10        2304 :     il[K] -= 1
      11             : 
      12             :     # NOTE: Need to convert back to SVector or won't optimize!
      13             : 
      14        2304 :     (F[SVector(ir)...] - F[SVector(il)...]) / 2h[K]
      15             : end
      16             : 
      17             : # left boundary
      18         640 : function _diffl(F, h, i::Tuple, ::Val{K}) where {K}
      19         640 :     ia = MVector(i...)
      20         640 :     ia[K] += 1
      21         640 :     ib = MVector(i...)
      22         640 :     ib[K] += 1
      23             : 
      24         640 :     (-3F[SVector(i)...] + 4F[SVector(ia)...] - 1F[SVector(ib)...]) / 2h[K]
      25             : end
      26             : 
      27             : # right boundary
      28         640 : function _diffr(F, h, i::Tuple, ::Val{K}) where {K}
      29         640 :     ia = MVector(i...)
      30         640 :     ia[K] -= 1
      31         640 :     ib = MVector(i...)
      32         640 :     ib[K] -= 1
      33             : 
      34         640 :     (+3F[SVector(i)...] - 4F[SVector(ia)...] + 1F[SVector(ib)...]) / 2h[K]
      35             : end
      36             : 
      37             : #-----------------------------------------------------------------------------
      38             : 
      39             : # Helper that handles edge cases:
      40             : #
      41             : # eigvals(S) requires a symmetric matrix S and raises an error otherwise.
      42             : # This includes the case that an element is NaN (which always implies that
      43             : # S != S').
      44             : #
      45             : # _maximum_eigvals(S) returns NaN if S is not symmetric or any entry is not
      46             : # finite (NaN or Inf).
      47             : #
      48        1536 : function _maximum_eigvals(S)
      49        1536 :     (S == S') || return eltype(S)(NaN)
      50        1536 :     maximum(eigvals(S))
      51             : end
      52             : 
      53        1024 : function _ftle(Φ, absτ, h::Tuple, i::Int, j::Int)
      54           0 :     @massert absτ > 0
      55             :     # Φx = (Φ[i+1, j] - Φ[i-1, j]) / 2h[1]
      56             :     # Φy = (Φ[i, j+1] - Φ[i, j-1]) / 2h[2]
      57             : 
      58        1920 :     Φx = _diff(Φ, h, (i, j), Val(1))
      59        1920 :     Φy = _diff(Φ, h, (i, j), Val(2))
      60             : 
      61        1024 :     ∇Φ = [ Φx Φy ] :: SMatrix
      62             : 
      63        1024 :     λmax = _maximum_eigvals(∇Φ'∇Φ)
      64             : 
      65        1024 :     log(λmax) / 2absτ
      66             : end
      67             : 
      68         512 : function _ftle(Φ, absτ, h::Tuple, i::Int, j::Int, k::Int)
      69           0 :     @massert absτ > 0
      70             : 
      71             :     # Φx = (Φ[i+1, j, k] - Φ[i-1, j, k]) / 2h[1]
      72             :     # Φy = (Φ[i, j+1, k] - Φ[i, j-1, k]) / 2h[2]
      73             :     # Φz = (Φ[i, j, k+1] - Φ[i, j, k+1]) / 2h[3]
      74             : 
      75         896 :     Φx = _diff(Φ, h, (i, j, k), Val(1))
      76         896 :     Φy = _diff(Φ, h, (i, j, k), Val(2))
      77         896 :     Φz = _diff(Φ, h, (i, j, k), Val(3))
      78             : 
      79         512 :     ∇Φ = [ Φx Φy Φz] :: SMatrix
      80             : 
      81        1024 :     λmax = _maximum_eigvals(∇Φ'∇Φ)
      82             : 
      83         512 :     log(λmax) / 2absτ
      84             : end
      85             : 
      86             : #-----------------------------------------------------------------------------
      87             : 
      88             : """
      89             :     value = _ftle(Φ, absτ, h, i, j)    # 2d
      90             :     value = _ftle(Φ, absτ, h, i, j, k) # 3d
      91             : 
      92             : Helper that computes FTLE value for sampled flow map `Φ` and `absτ =
      93             : abs(τ) > 0` and grid spacing `h = (hi, hi, ...)` at `i, j, ...`.
      94             : 
      95             : See also [`ftle`](@ref), [`ftle!`](@ref)
      96             : """ _ftle
      97             : 
      98          12 : function ftle(p::Problem{N, T, S, Dy, Out},
      99             :                          t0::T, τ::T, extent...) where {N, T, S, Dy, Out}
     100          12 :     @assert 2 <= N::Int <= 3 "require dimensions N in [2, 3]"
     101          12 :     sz = length.(extent)
     102          12 :     Φ = Array{SVector{N, T}, N}(undef, sz...)
     103          12 :     F = Array{T, N}(undef, sz...)
     104             : 
     105          12 :     ftle!(p, Φ, F, t0, τ, extent...)
     106             : end
     107             : 
     108           2 : function ftle(p::Problem{N, T, S, Dy, Out},
     109             :                          t0::T, τs, extent...) where {N, T, S, Dy, Out}
     110           2 :     @assert 2 <= N::Int <= 3 "require dimensions N in [2, 3]"
     111           2 :     sz = length.(extent)
     112           2 :     n = length(τs)
     113           2 :     Φ = Array{SVector{N, T}, N + 1}(undef, n, sz...)
     114           2 :     F = Array{T, N + 1}(undef, n, sz...)
     115             : 
     116           2 :     ftle!(p, Φ, F, t0, τs, extent...)
     117             : end
     118             : 
     119             : # 2d single τ
     120          16 : function ftle!(p::Problem{2, T, S, Dy, Out},
     121             :                Φ::AbstractArray{SVector{2, T}, 2}, F::Array{T, 2},
     122             :                t0::T, τ::T,
     123             :                x::StepRangeLen{T}, y::StepRangeLen{T};
     124             :                sample::Bool=true) where {T, S, Dy, Out}
     125           8 :     sz = (length(x), length(y))
     126           8 :     @assert size(Φ) == sz
     127           8 :     @assert size(F) == sz
     128             : 
     129           8 :     sample && sampleflowmap!(p, Φ, t0, t0 + τ, x, y)
     130             : 
     131           8 :     h = (x[2] - x[1], y[2] - y[1])
     132           8 :     τ = abs(τ)
     133             : 
     134           8 :     @floop for i in 1:sz[1], j in 1:sz[2] # TODO: [benchmark] parallel should be dim k?!
     135         512 :         F[i, j] = _ftle(Φ, τ, h, i, j)
     136           0 :     end
     137             : 
     138           8 :     F
     139             : end
     140             : 
     141             : # 2d multiple τs
     142           2 : function ftle!(p::Problem{2, T, S, Dy, Out},
     143             :                Φ::AbstractArray{SVector{2, T}, 3}, F::AbstractArray{T, 3},
     144             :                t0::T, τs,
     145             :                x::StepRangeLen{T}, y::StepRangeLen{T};
     146             :                sample::Bool=true) where {T, S, Dy, Out}
     147           1 :     sz = (length(x), length(y))
     148           1 :     n = length(τs)
     149           1 :     @assert size(Φ) == (n, sz...)
     150           1 :     @assert size(F) == (n, sz...)
     151             : 
     152           1 :     t1 = t0 .+ τs
     153             : 
     154           1 :     sample && sampleflowmap!(p, Φ, t0, t1, x, y)
     155             : 
     156           1 :     h = (x[2] - x[1], y[2] - y[1])
     157             : 
     158           1 :     @floop for ti in 1:n
     159           8 :         τ = abs(τs[ti])
     160           8 :         Φh = @view Φ[ti, :, :]
     161             : 
     162           8 :         for i in 1:sz[1], j in 1:sz[2]
     163         512 :             F[ti, i, j] = _ftle(Φh, τ, h, i, j)
     164         568 :         end
     165           0 :     end
     166             : 
     167           1 :     F
     168             : end
     169             : 
     170             : # 3d single τ
     171           8 : function ftle!(p::Problem{3, T, S, Dy, Out},
     172             :                Φ::Array{SVector{3, T}, 3}, F::Array{T, 3},
     173             :                t0::T, τ::T,
     174             :                x::StepRangeLen{T}, y::StepRangeLen{T}, z::StepRangeLen{T};
     175             :                sample::Bool=true) where {T, S, Dy, Out}
     176           4 :     sz = (length(x), length(y), length(z))
     177           4 :     @assert size(Φ) == sz
     178           4 :     @assert size(F) == sz
     179             : 
     180           4 :     sample && sampleflowmap!(p, Φ, t0, t0 + τ, x, y, z)
     181             : 
     182           4 :     h = (x[2] - x[1], y[2] - y[1], z[2] - z[1])
     183           4 :     τ = abs(τ)
     184             : 
     185           4 :     @floop for i in 1:sz[1], j in 1:sz[2], k in 1:sz[3] # TODO: [benchmark] parallel should be dim k?!
     186         256 :         F[i, j, k] = _ftle(Φ, τ, h, i, j, k)
     187           0 :     end
     188             : 
     189           4 :     F
     190             : end
     191             : 
     192             : # 3d multiple τs
     193           2 : function ftle!(p::Problem{3, T, S, Dy, Out},
     194             :                Φ::AbstractArray{SVector{3, T}, 4}, F::AbstractArray{T, 4},
     195             :                t0::T, τs,
     196             :                x::StepRangeLen{T}, y::StepRangeLen{T}, z::StepRangeLen{T};
     197             :                sample::Bool=true) where {T, S, Dy, Out}
     198           1 :     sz = (length(x), length(y),length(z))
     199           1 :     n = length(τs)
     200           1 :     @assert size(Φ) == (n, sz...)
     201           1 :     @assert size(F) == (n, sz...)
     202             : 
     203           1 :     t1 = t0 .+ τs
     204             : 
     205           1 :     sample && sampleflowmap!(p, Φ, t0, t1, x, y, z)
     206             : 
     207           1 :     h = (x[2] - x[1], y[2] - y[1], z[2] - z[1])
     208             : 
     209           1 :     @floop for ti in 1:n
     210           4 :         τ = abs(τs[ti])
     211           4 :         Φh = @view Φ[ti, :, :, :]
     212             : 
     213           4 :         for i in 1:sz[1], j in 1:sz[2], k in 1:sz[3]
     214         256 :             F[ti, i, j, k] = _ftle(Φh, τ, h, i, j, k)
     215         268 :         end
     216           0 :     end
     217             : 
     218           1 :     F
     219             : end
     220             : 
     221             : """
     222             :     x = range(x0, x1, length = M)
     223             :     y = range(y0, y1, length = N)
     224             :     z = range(z0, z1, length = K)
     225             : 
     226             :     F = ftle(problem, t0, τ, x, y[, z])
     227             :     F = ftle(problem, t0, τs, x, y[, z])
     228             : 
     229             : Sample 2d or 3d FTLE field in `F` for `problem` and integation from
     230             : `t0` to `t0 + τ` (or for each `τ ∈ τs`) in domain `x × y[ ×z]`.
     231             : 
     232             : !!! note
     233             :     Non-finite entries in `Φ` produce undefined FTLE values (`NaN`).
     234             : 
     235             : !!! note
     236             :     FTLE is computed at the same resolution as the flow map with
     237             :     adpated rules for central differences at the boundary.
     238             : 
     239             : !!! note
     240             :     `ftle` uses **multi-threading** ([`mtproblem`](@ref)).
     241             : 
     242             : See also [`ftle!`](@ref)
     243             : """ ftle
     244             : 
     245             : """
     246             :     x = range(x0, x1, length = M)
     247             :     y = range(y0, y1, length = N)
     248             :     z = range(z0, z1, length = K)
     249             : 
     250             :     ftle!(problem, Φ, F, t0, τ, x, y[, z; sample=true])
     251             :     ftle!(problem, Φ, F, t0, τs, x, y[, z; sample=true])
     252             : 
     253             : In-place variant of [`ftle`](@ref) that samples flow map in `Φ` and
     254             : stores FTLE values in `F`.
     255             : 
     256             : If `sample=false`, the flow map is not sampled and `Φ` is treated as
     257             : input.
     258             : 
     259             : !!! note
     260             :     Non-finite entries in `Φ` produce undefined FTLE values (`NaN`).
     261             : 
     262             : !!! note
     263             :     Dimensions of `Φ` and `F` must match the lengths `M, N, K`.
     264             : 
     265             : See also [`ftle`](@ref), [`sampleflowmap!`](@ref)
     266             : """ ftle!
     267             : 
     268             : # TODO: [test] test FTLE (against DifferentialEquations)

Generated by: LCOV version 1.16