LCOV - code coverage report
Current view: top level - src/geometry - barycoord.jl (source / functions) Hit Total Coverage
Test: on branch nothing Lines: 39 42 92.9 %
Date: 2025-07-10 13:12:25 Functions: 0 0 -

          Line data    Source code
       1             : module BarycentricCoordinates
       2             : 
       3             : using LinearAlgebra
       4             : using StaticArrays
       5             : 
       6             : #
       7             : # Define simplex for barycentric interpolation
       8             : #
       9             : 
      10             : """
      11             : Simplex for barycentric interpolation as a callable object.
      12             : 
      13             :     f = baryint(...) :: Simplex
      14             :     y = f(λ)
      15             :     y = f(λ₁, λ₂, ...)
      16             : 
      17             : See also [`baryint`](@ref), [`MapBaryCoords`](@ref), [`mapbarycoords`](@ref)
      18             : """
      19             : struct Simplex{M, T, N, L} # NOTE: Must specify L for (or dynamic dispatch!)!
      20         139 :     X::SMatrix{M, N, T, L}
      21             : end
      22             : 
      23          13 : baryint(a::V, b::V) where {V} =
      24             :     Simplex{length(a), eltype(V), 2, 2length(a)}(SMatrix{length(a), 2}(a..., b...))
      25             : 
      26         126 : baryint(a::V, b::V, c::V) where {V} =
      27             :     Simplex{length(a), eltype(V), 3, 3length(a)}(SMatrix{length(a), 3}(a..., b..., c...))
      28             : 
      29             : """
      30             :    f = baryint(x1, x2, ...)
      31             :    f = baryint((x1, x2, ...))
      32             : 
      33             :    y = f(λ)
      34             :    y = f(λ₁, λ₂, ...)
      35             : 
      36             : Get callable object `f` that applied barycentric interpolation
      37             : `[x1 x2 ...] * λ` with `sum(λ) == 1`.
      38             : 
      39             : !!! warning "precondition"
      40             :     Requires `sum(λ) == 1`. This is *not* checked!
      41             : 
      42             : !!! note
      43             :     [`baryint`](@ref) supports only line segments and triangles,
      44             :     both in 2d and 3d.
      45             : 
      46             : See also [`Simplex`](@ref), [`mapbarycoords`](@ref), [`barycoords`](@ref)
      47             : """ baryint
      48             : 
      49        3677 : (b::Simplex{M, T, N})(λ::SVector{N, T}) where {M, T, N} = b.X * λ
      50             : 
      51           7 : (b::Simplex{M, T, N})(λ::AbstractVector) where {M, T, N} =
      52             :     b(SVector{N, T}(λ...))
      53             : 
      54           0 : (b::Simplex)(λ) = b(SVector(λ...))
      55             : 
      56           0 : (b::Simplex)(λ::R, λs...) where {R<:Number} = b.X * SVector(λ, λs...)
      57             : 
      58             : #
      59             : # Get affine map that computes barycentric coordinates
      60             : #
      61             : 
      62             : """
      63             : Affine map that computes barycentric coordinates.
      64             : 
      65             :    b = mapbarycoords(x1, x2, ...)
      66             : 
      67             :    f = baryint(x1, x2, ...) :: Simplex
      68             :    b = mapbarycoords(f) :: MapBaryCoords
      69             :    λ = b(x)
      70             :    y = f(λ)
      71             : 
      72             :    λ = barycoords(f, x)
      73             : 
      74             :    @assert sum(λ) ≈ 1
      75             : 
      76             : In case of a `n`-simplex `f` in `m>n` dimensions, the map "assumes"
      77             : that is `x` is in the affine subspace defined by `f`. If this is not
      78             : the case, there exist no barycentric coordinates, and the map computes
      79             : barycentric coordinates for a *projection* into the affine
      80             : subspace. Note that in this case, we have `f(λ) != x`.
      81             : 
      82             : In case the simplex `f` is *degenerated*, there exist no barycentric
      83             : coordinates. In this case, the map computes a "best fit":
      84             : 
      85             : !!! note
      86             :     Mapping `x` to barycentric coordinates `λ` *never fails*, i.e.,
      87             :     yields *always finite* barycentric coordinates `λ`. Consequently,
      88             :     one may have `f(λ) != x`.
      89             : 
      90             : See also [`mapbarycoords`](@ref), [`baryint`](@ref), [`Simplex`](@ref),
      91             : [`barycoords`](@ref)
      92             : """
      93             : struct MapBaryCoords{M, N, D, T, L}
      94        2252 :     A::SMatrix{M, N, T, L}
      95             :     x0::SVector{D, T}
      96             : end
      97             : 
      98        3793 : function (bc::MapBaryCoords)(x)
      99        3793 :     λ = bc.A * (x - bc.x0)
     100        3793 :     SVector(λ..., 1-sum(λ))
     101             : end
     102             : 
     103             : """
     104             :    f = baryint(x1, x2, ...) :: Simplex
     105             :    λ = barycoords(f, x)
     106             : 
     107             :    @assert sum(λ) ≈ 1
     108             : 
     109             : Evaluate barycentric coordinates `λ` for `x` in `f`.
     110             : 
     111             : !!! note
     112             :     [`mapbarycoords`](@ref) constructs an affine map from `x` to `λ`.
     113             :     This is more efficient, if multiple `x` are mapped for the same
     114             :     simplex.
     115             : 
     116             : See also [`mapbarycoords`](@ref)
     117             : """
     118        2170 : barycoords(s::Simplex, x) = mapbarycoords(s)(x)
     119             : 
     120           0 : mapbarycoords(x::V, y::V, z...) where {V<:AbstractVector} =
     121             :     mapbarycoords(baryint(x, y, z...))
     122             : 
     123         110 : function mapbarycoords(s::Simplex{N, T, 2}) where {N, T}
     124         110 :     x0 = s.X[:, 2]
     125         110 :     a = s.X[:, 1] - x0
     126             : 
     127         110 :     len2 = dot(a, a)
     128             : 
     129         110 :     (len2 > 16eps()) || (len2 = one(T))
     130             : 
     131         110 :     MapBaryCoords{1, N, N, T, N}(a / len2, x0)
     132             : end
     133             : 
     134        1480 : function mapbarycoords(s::Simplex{2, T, 3}) where {T}
     135        1480 :     x0 = s.X[:, 3]
     136        1480 :     A = hcat(s.X[:, 1]-x0, s.X[:, 2]-x0)
     137             : 
     138        1480 :     invA = inv(A)
     139        1500 :     all(isfinite, invA) || (invA = pinv(A))
     140             : 
     141        1480 :     MapBaryCoords{2, 2, 2, T, 4}(invA, x0)
     142             : end
     143             : 
     144         662 : function mapbarycoords(s::Simplex{3, T, 3}) where {T}
     145         662 :     x0 = s.X[:, 3]
     146         662 :     u = s.X[:, 1] - x0
     147         662 :     v = s.X[:, 2] - x0
     148             : 
     149         662 :     w = abs.(u × v)
     150        1986 :     i = argmax(w)
     151             : 
     152         662 :     if w[i] < eps()
     153          60 :         i = argmin(abs(u[i]) + abs(v[i]) for i in 1:length(u))
     154             :     end
     155             : 
     156         662 :     A, P =
     157             :         if i == 1
     158         199 :             @SMatrix([ u[2] v[2]; u[3] v[3] ]), SMatrix{2, 3, T}(0, 0, 1, 0, 0, 1)
     159         463 :         elseif i == 2
     160         190 :             @SMatrix([ u[1] v[1]; u[3] v[3] ]), SMatrix{2, 3, T}(1, 0, 0, 0, 0, 1)
     161             :         else
     162        1125 :             @SMatrix([ u[1] v[1]; u[2] v[2] ]), SMatrix{2, 3, T}(1, 0, 0, 1, 0, 0)
     163             :         end
     164             : 
     165             :     # TODO: test projections (add tests below)
     166             :     # TODO: update/assemble matrix inv(A) in-place (instead of inv(A)*P)
     167             : 
     168         662 :     invA =
     169             :         if w[i] < eps()
     170          40 :             pinv(A)
     171             :         else
     172        1304 :             inv(A)
     173             :         end
     174             : 
     175         662 :     MapBaryCoords{2, 3, 3, T, 6}(invA * P, x0)
     176             : end
     177             : 
     178             : """
     179             :    b = mapbarycoords(x1, x2, ...)
     180             : 
     181             :    f = baryint(x1, x2, ...) :: Simplex
     182             :    b = mapbarycoords(f) :: MapBaryCoords
     183             :    λ = b(x)
     184             :    y = f(λ)
     185             : 
     186             :    λ = barycoords(f, x)
     187             : 
     188             : Get affine map [`MapBaryCoords`](@ref) that maps `x` to barycentric
     189             : coordinates `λ`.
     190             : 
     191             : !!! note
     192             :     For computing barycentric coordinates for *one single* point `x`,
     193             :     [`barycoords`](@ref) may be more convenient.
     194             : 
     195             : In case of a `n`-simplex `f` in `m>n` dimensions, the map "assumes"
     196             : that is `x` is in the affine subspace defined by `f`. If this is not
     197             : the case, there exist no barycentric coordinates, and the map computes
     198             : barycentric coordinates for a *projection* into the affine
     199             : subspace. Note that in this case, we have `f(λ) != x`.
     200             : 
     201             : In case the simplex `f` is *degenerated*, there exist no barycentric
     202             : coordinates. In this case, the map computes a "best fit":
     203             : 
     204             : !!! note
     205             :     Mapping `x` to barycentric coordinates `λ` *never fails*, i.e.,
     206             :     yields *always finite* barycentric coordinates  `λ`. Consequently,
     207             :     one may have `f(λ) != x`.
     208             : 
     209             : See [`MapBaryCoords`](@ref), [`barycoords`](@ref), [`baryint`](@ref)
     210             : """ mapbarycoords
     211             : 
     212             : end

Generated by: LCOV version 1.16