Skip to content

jehicken/LevelSets.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LevelSets

Stable Dev Build Status Coverage

This package implements the level-set formula described in Hicken J. and Kaur S., "An Explicit Level-Set Formula to Approximate Geometries," Proceedings of the AIAA 2022 SciTech Forum. Please see the paper and/or the video for further details.

This is an unregistered package, so you will need to follow these steps from the Julia documentation to add LevelSets.jl.

The tests in test/runtestss.jl offer some examples of how to use the package. The following module provides a model application to the ellipsoid that might be helpful. You will need the WriteVTK and PyPlot packages to run the functions in this example.

module Ellipsoid

using WriteVTK
using LinearAlgebra
using LevelSets
using PyPlot

export plotEllipsoid, ellipsoidConverge

"""
    kappa1, kappa2, lambda1, lambda2 = principals(E, F, G, L, M, N)

Compute the principal curvatures, `kappa1` and `kappa2`, and principal 
directions, `lambda1` and `lambda2`, using the first- and second-
fundamental forms.
"""
function principals(E::Float64, F::Float64, G::Float64, L::Float64,
                    M::Float64, N::Float64, )
    denom_fac = 1/(E*G - F^2)
    K = (L*N - M^2)*denom_fac 
    H = (E*N + G*L -2*F*M)*denom_fac/2.0
    kappa1 = H + sqrt(H^2 - K)
    kappa2 = H - sqrt(H^2 - K)
    lambda1 = ones(2)
    lambda2 = ones(2)
    tol = 1e-10
    if abs(N - kappa1*G) > tol
        lambda1[2] = - (M - kappa1*F)/(N - kappa1*G)
    elseif abs(M - kappa1*F) > tol
        lambda1[2] = - (L - kappa1*E)/(M - kappa1*F)
    else 
        lambda1[2] = 1.0
        lambda1[1] = 0.0
    end 
    if abs(N - kappa2*G) > tol
        lambda2[2] = - (M - kappa2*F)/(N - kappa2*G)
    elseif abs(M - kappa2*F) > tol
        lambda2[2] = - (L - kappa2*E)/(M - kappa2*F)
    else 
        lambda2[2] = 1.0
        lambda2[1] = 0.0
    end
    return kappa1, kappa2, lambda1, lambda2
end

"""
    plotEllipsoid(numu, numv, rhofactor[, quad=false, a=2.0, b=0.5, c=1.0])

Create a LSF for the ellipsoid (with axes lengths `a`, `b`, and `c`), and save a VTK file of it for visualization. 
"""
function plotEllipsoid(numu::Int, numv::Int, rhofactor::Float64; 
                       quad::Bool=false, a::Float64=2.0, b::Float64=0.5, 
                       c::Float64=1.0)
    
    # sample the ellipsoid to get the centers and frame 
    numbasis = numu*numv 
    xc = zeros(3, numbasis)
    nrm = zero(xc)
    tang = zeros(3, 2, numbasis)
    crv = zeros(2, numbasis)
    min_min = 1e100
    max_max = 0.0
    max_ratio = 0.0
    for i = 1:numu 
        u = 2*pi*(i-0.5)/numu
        for j = 1:numv 
            v = pi*(j-0.5)/numv 
            n = numv*(i-1) + j
            xc[:, n] = [a*cos(u)*sin(v); b*sin(u)*sin(v); -c*cos(v)]
            du = [-a*sin(u)*sin(v); b*cos(u)*sin(v); 0.0]
            dv = [a*cos(u)*cos(v); b*sin(u)*cos(v); c*sin(v)]
            du2 = [-a*cos(u)*sin(v); -b*sin(u)*sin(v); 0.0]
            dv2 = [-a*cos(u)*sin(v); -b*sin(u)*sin(v); c*cos(v)]
            dudv = [-a*sin(u)*cos(v); b*cos(u)*cos(v); 0.0]
            nrm[:, n] = cross(du, dv)
            fac = 1/norm(nrm[:,n])
            # compute the terms for the first and second fundamental forms 
            E = dot(du, du)
            F = dot(du, dv) 
            G = dot(dv, dv) 
            L = dot(du2, nrm[:,n])*fac
            M = dot(dudv, nrm[:,n])*fac 
            N = dot(dv2, nrm[:,n])*fac 
            # compute the principal curvatures/directions
            kappa1, kappa2, lambda1, lambda2 = principals(E, F, G, L, M, N)
            tang[:,1,n] = lambda1[1]*du + lambda1[2]*dv 
            tang[:,2,n] = lambda2[1]*du + lambda2[2]*dv
            println("E, F, G, L, M, N = ",E,", ",F,", ",G,", ",L,", ",M,", ",N)
            println("tang1 dot tang2 = ", dot(tang[:,1,n], tang[:,2,n]))
            @assert( abs(dot(tang[:,1,n], tang[:,2,n])) < 1e-9,
            "tangents are not orthogonal!" )
            if quad 
                crv[1,n] = -kappa1 
                crv[2,n] = -kappa2
                global max_ratio = max(max_ratio, abs(kappa1/kappa2), abs(kappa2/kappa1))
                global min_min = min(min_min, abs(kappa1), abs(kappa2))
                global max_max = max(max_max, abs(kappa1), abs(kappa2))
            else 
                crv[1,n] = 0.0
                crv[2,n] = 0.0
            end
        end 
    end 
    println("miniumum prin. curvature = ", min_min)
    println("maximum prin. curvature = ",max_max)
    println("max curvature ratio = ",max_ratio)
    
    # construct the LevelSet 
    rho = rhofactor*convert(Float64, numu)
    levset = LevelSet{3,Float64}(xc, nrm, tang, crv, rho)
    
    # write the centers to a file 
    centers = Vector{MeshCell{VTKCellType, Vector{Int64}}}(undef, levset.numbasis)
    for i = 1:levset.numbasis 
        centers[i] = MeshCell(VTKCellTypes.VTK_VERTEX, [i])
    end
    normal = view(levset.frame, :, 1, :)
    saved_files = vtk_grid("centers.vtu", levset.xcenter, centers) do vtk
        vtk["normal"] = normal
    end
    
    # Next, we make a fine mesh in (u,v) space for plotting in Paraview 
    numu = 1200
    numv = 1200
    numvert = numu*numv
    function getvertex(i, j)
        return numv*(j-1) + i
    end
    
    # get the points making up the vtk triangles
    points = zeros(3, numvert)
    for i = 1:numu 
        u = 2*pi*(i-1)/(numu-1)
        for j = 1:numv 
            v = pi*(j-1)/(numv-1)
            n = getvertex(i, j)
            x0 = [a*cos(u)*sin(v); b*sin(u)*sin(v); c*cos(v)]
            snappoint!(view(points,:,n), x0, levset, tol=1e-8, max_newton=50, 
            silent_fail=true)
        end
    end
    
    # define the triangles 
    numcell = 2*(numu-1)*(numv-1)
    cells = Vector{MeshCell{VTKCellType, Vector{Int64}}}(undef, numcell)
    for i = 1:numu-1
        for j = 1:numv-1
            n = (numv-1)*(j-1) + i
            connect = [getvertex(i,j), getvertex(i+1,j), getvertex(i,j+1)]
            cells[n] = MeshCell(VTKCellTypes.VTK_TRIANGLE, connect)
            n += (numu-1)*(numv-1)
            connect = [getvertex(i+1,j+1), getvertex(i,j+1), getvertex(i+1,j)]
            cells[n] = MeshCell(VTKCellTypes.VTK_TRIANGLE, connect)
        end
    end
    
    # save the vtk unstructured grid file
    saved_files = vtk_grid("ellipse.vtu", points, cells) do vtk
        # add datasets here...nothing to add in this case
    end
    
end

"""
    ellipsoidConverge([a=2.0, b=0.5, c=1.0])

Run the ellipsoid convergence study from the paper.
"""
function ellipsoidConverge(;a::Float64=2.0, b::Float64=0.5, c::Float64=1.0)

    # set the points used to measure error 
    numu, numv = 1200, 1200
    numpts = numu*numv
    x = zeros(3, numpts)
    ptr = 1
    for i = 1:numu 
        u = 2*pi*(i-0.5)/numu
        for j = 1:numv 
            v = pi*(j-0.5)/numv            
            x[:,ptr] = [a*cos(u)*sin(v); b*sin(u)*sin(v); c*cos(v)]
            ptr += 1
        end
    end
    res = zeros(size(x,2))

    numu = [10, 20, 40, 80, 160]
    rhofactor = [1, 10]
    l2err = zeros(size(numu,1), size(rhofactor,1))
    l2err_ho = zeros(size(numu,1), size(rhofactor,1))
    for (k, nu) in enumerate(numu)
        # set basis centers and normals
        numbasis = nu*nu 
        xc = zeros(3, numbasis)
        nrm = zeros(3, numbasis)
        tang = zeros(3, 2, numbasis)
        crv = zeros(2, numbasis)
        ptr = 1
        for i = 1:nu
            u = 2*pi*(i-0.5)/nu
            for j = 1:nu
                v = pi*(j-0.5)/nu 
                xc[:, ptr] = [a*cos(u)*sin(v); b*sin(u)*sin(v); -c*cos(v)]
                du = [-a*sin(u)*sin(v); b*cos(u)*sin(v); 0.0]
                dv = [a*cos(u)*cos(v); b*sin(u)*cos(v); c*sin(v)]
                du2 = [-a*cos(u)*sin(v); -b*sin(u)*sin(v); 0.0]
                dv2 = [-a*cos(u)*sin(v); -b*sin(u)*sin(v); c*cos(v)]
                dudv = [-a*sin(u)*cos(v); b*cos(u)*cos(v); 0.0]
                nrm[:,ptr] = cross(du, dv)
                fac = 1/norm(nrm[:,ptr])
                # compute the terms for the first and second fundamental forms 
                E = dot(du, du)
                F = dot(du, dv)
                G = dot(dv, dv)
                L = dot(du2, nrm[:,ptr])*fac
                M = dot(dudv, nrm[:,ptr])*fac 
                N = dot(dv2, nrm[:,ptr])*fac 
                # compute principal curvatures/directions
                kappa1, kappa2, lambda1, lambda2 = principals(E, F, G, L, M, N)
                tang[:,1,ptr] = lambda1[1]*du + lambda1[2]*dv 
                tang[:,2,ptr] = lambda2[1]*du + lambda2[2]*dv
                crv[1,ptr] = -kappa1 
                crv[2,ptr] = -kappa2
                ptr += 1
            end 
        end

        for (j, r) in enumerate(rhofactor)
            rho = r*convert(Float64, nu)

            # linear approximation 
            levset = LevelSet{3,Float64}(xc, nrm, tang, zeros(size(crv)), rho)
            LevelSets.residual!(res, x, levset)
            l2err[k,j] = sqrt(dot(res,res)/numpts)
            println("Linear: L2 integral norm = ",l2err[k,j])

            # quadratic approximation 
            levset = LevelSet{3,Float64}(xc, nrm, tang, crv, rho)
            LevelSets.residual!(res, x, levset)
            l2err_ho[k,j] = sqrt(dot(res,res)/numpts)
            println("Quadratic: L2 integral norm = ",l2err_ho[k,j])
        end
    end

    PyPlot.figure(figsize=[6,2.5])

    linear_style = ["-ro", "--ro", ":ro"]
    quad_style = ["-ks", "--ks", ":ks"]
    for (j, r) in enumerate(rhofactor)
        label = L"linear: $\rho/\sqrt{n_{\Gamma}} = " * string(r) * L"$"
        PyPlot.plot(numu, l2err[:,j], linear_style[j], linewidth=1, 
                    markersize=4, label=label)
    end
    for (j, r) in enumerate(rhofactor)
        label = L"quadratic: $\rho/\sqrt{n_{\Gamma}} = " * string(r) * L"$"
        PyPlot.plot(numu, l2err_ho[:,j], quad_style[j], linewidth=1, 
                    markersize=4,label=label)
    end
    PyPlot.gca().axis([5, 200, 1e-7, 0.1])
    PyPlot.gca().set_position([0.12, 0.17, 0.5, 0.8])
    PyPlot.gca().grid(which="major", linestyle=":")
    PyPlot.xticks(fontsize=8)
    PyPlot.yticks(fontsize=8)
    PyPlot.xscale("log")
    PyPlot.yscale("log")
    PyPlot.xlabel(L"$\sqrt{n_{\Gamma}}$", labelpad=4, fontsize=10)
    PyPlot.ylabel("RMS Error", labelpad=12, fontsize=10)
    PyPlot.gca().legend(loc=(1.1, 0.0), handlelength=4, labelspacing=1,
                        fontsize=8)

    PyPlot.savefig("ellipsoid-converge.png", dpi=300, transparent=true)
end

end # module Ellipsoid 

About

Draft library to investigate a level-set function

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages