Total Lagrangian MPM

# Particles# IterationsExecution time
8k27k1 min

This example demonstrates the total lagrangian material point method[1]. The implementation solves generalized vortex problem[1] using a linear kernel.

Note

Currently, the Bernstein function used in the paper[1] has not been implemented.

using Tesserae

function main()

    # Simulation parameters
    h   = 0.02 # Grid spacing
    T   = 1.0  # Time span
    CFL = 0.1  # Courant number
    α   = 0.99 # PIC-FLIP parameter

    # Material constants
    E  = 1e6                    # Young's modulus
    ν  = 0.3                    # Poisson's ratio
    λ  = (E*ν) / ((1+ν)*(1-2ν)) # Lame's first parameter
    μ  = E / 2(1 + ν)           # Shear modulus
    ρ⁰ = 1e3                    # Initial density

    # Geometry
    Rᵢ = 0.75
    Rₒ = 1.25

    # Equations for vortex
    G = π
    R̄ = (Rᵢ + Rₒ) / 2
    function calc_b_Rθ(R, t)
        local h′′, h′, h = hessian(R -> 1-8((R-R̄)/(Rᵢ-Rₒ))^2+16((R-R̄)/(Rᵢ-Rₒ))^4, R, :all)
        local g′′, g′, g = hessian(t -> G*sin(π*t/T), t, :all)
        β = g * h
        b_R = ( μ/ρ⁰*(3g*h′+R*g*h′′) - R*g′′*h)*sin(β) + (μ/ρ⁰*R*(g*h′)^2 - R*(g′*h)^2)*cos(β)
        b_θ = (-μ/ρ⁰*(3g*h′+R*g*h′′) + R*g′′*h)*cos(β) + (μ/ρ⁰*R*(g*h′)^2 + R*(g′*h)^2)*sin(β)
        Vec(b_R, b_θ)
    end
    isinside(x::Vec) = Rᵢ^2 < x⋅x < Rₒ^2

    GridProp = @NamedTuple begin
        X    :: Vec{2, Float64}
        m    :: Float64
        m⁻¹  :: Float64
        mv   :: Vec{2, Float64}
        fint :: Vec{2, Float64}
        fext :: Vec{2, Float64}
        b    :: Vec{2, Float64}
        v    :: Vec{2, Float64}
        vⁿ   :: Vec{2, Float64}
    end
    ParticleProp = @NamedTuple begin
        x  :: Vec{2, Float64}
        X  :: Vec{2, Float64}
        m  :: Float64
        V⁰ :: Float64
        v  :: Vec{2, Float64}
        ṽ  :: Vec{2, Float64}
        ã  :: Vec{2, Float64}
        P  :: SecondOrderTensor{2, Float64, 4}
        F  :: SecondOrderTensor{2, Float64, 4}
    end

    # Background grid
    grid = generate_grid(GridProp, CartesianMesh(h, (-1.5,1.5), (-1.5,1.5)))
    outside_gridinds = findall(!isinside, grid.X)

    # Particles
    particles = generate_particles(ParticleProp, grid.X; alg=GridSampling(spacing=1))
    particles.V⁰ .= volume(grid.X) / length(particles)

    filter!(pt->isinside(pt.x), particles)

    @. particles.X = particles.x
    @. particles.m = ρ⁰ * particles.V⁰
    @. particles.F = one(particles.F)
    @show length(particles)

    # Precompute linear kernel values
    mpvalues = generate_mpvalues(BSpline(Linear()), grid.X, length(particles))
    for p in eachindex(particles, mpvalues)
        update!(mpvalues[p], particles.x[p], grid.X)
    end

    # Outputs
    outdir = mkpath(joinpath("output", "tlmpm_vortex"))
    pvdfile = joinpath(outdir, "paraview")
    closepvd(openpvd(pvdfile)) # Create file

    t = 0.0
    step = 0
    fps = 60
    savepoints = collect(LinRange(t, T, round(Int, T*fps)+1))

    Tesserae.@showprogress while t < T

        # Calculate time step based on the wave speed
        vmax = maximum(@. sqrt((λ+2μ) / (particles.m/(particles.V⁰ * det(particles.F)))) + norm(particles.v))
        Δt = CFL * h / vmax

        # Compute grid body forces
        for i in eachindex(grid)
            if isinside(grid.X[i])
                (x, y) = grid.X[i]
                R = sqrt(x^2 + y^2)
                θ = atan(y, x)
                grid.b[i] = rotmat(θ) * calc_b_Rθ(R, t)
            end
        end

        # Particle-to-grid transfer
        @P2G grid=>i particles=>p mpvalues=>ip begin
            m[i]    = @∑ w[ip] * m[p]
            mv[i]   = @∑ w[ip] * m[p] * v[p]
            fint[i] = @∑ -V⁰[p] * P[p] * ∇w[ip]
        end

        # Update grid velocity
        @. grid.m⁻¹  = inv(grid.m) * !iszero(grid.m)
        @. grid.fext = grid.m * grid.b
        @. grid.vⁿ   = grid.mv * grid.m⁻¹
        @. grid.v    = grid.vⁿ + ((grid.fint + grid.fext) * grid.m⁻¹) * Δt
        grid.v[outside_gridinds] .= Ref(zero(eltype(grid.v)))

        # Update particle velocity and position
        @G2P grid=>i particles=>p mpvalues=>ip begin
            ṽ[p]  = @∑ w[ip] * v[i]
            ã[p]  = @∑ w[ip] * (v[i] - vⁿ[i]) / Δt
            v[p]  = (1-α)*ṽ[p] + α*(v[p] + ã[p]*Δt)
            x[p] += ṽ[p] * Δt
        end

        # Remap updated velocity to grid (MUSL)
        @P2G grid=>i particles=>p mpvalues=>ip begin
            mv[i] = @∑ w[ip] * m[p] * v[p]
            v[i]  = mv[i] * m⁻¹[i]
        end
        grid.v[outside_gridinds] .= Ref(zero(eltype(grid.v)))

        # Update stress
        @G2P grid=>i particles=>p mpvalues=>ip begin
            F[p] += @∑ Δt * v[i] ⊗ ∇w[ip]
            P[p]  = μ * (F[p] - inv(F[p])') + λ * log(det(F[p])) * inv(F[p])'
        end

        t += Δt
        step += 1

        if t > first(savepoints)
            popfirst!(savepoints)
            openpvd(pvdfile; append=true) do pvd
                openvtm(string(pvdfile, step)) do vtm
                    angle(x) = atan(x[2], x[1])
                    openvtk(vtm, particles.x) do vtk
                        vtk["Velocity (m/s)"] = particles.v
                        vtk["Initial angle (rad)"] = angle.(particles.X)
                    end
                    openvtk(vtm, grid.X) do vtk
                        vtk["External force (N)"] = grid.fext
                    end
                    pvd[t] = vtm
                end
            end
        end
    end
end

This page was generated using Literate.jl.