Fun with Julia

I am a strong advocate of Julia, a new programming language for scientific computing. Julia combines the speed of C/Fortran with the user-friendliness of Python. In this first demonstration, I wrote a one-dimentional hydrodynamic code. This simulation, along with the plotting and animation, was produced entirely from a single Julia script, as demonstrated below.

tube-10fps.gif

Download script.

using Parameters 
using Plots
using Printf

""" A struct to store all parameters and data of the grids """
@with_kw mutable struct Grid
    nx::Int
    ng::Int
    t = 0.
    xmin = 0.
    xmax = 1.
    xlen = nx + 2 * ng
    gamma = 1.4
    C = 0
    jlo = ng + 1
    jhi = nx + ng
    # the grids
    dx = (xmax - xmin) / (nx - 1)
    x = xmin .+ (1:(nx + 2*ng) .- ng) * dx
    u = zeros(xlen, 3)
end

""" set the initial conditions """
function init!(g::Grid)
    mid = floor(Int16, g.xlen/2)
    g.u[1:mid, 1] .= 1.0
    g.u[(mid+1):end, 1] .= .125
    pressure = zeros(Float64, g.xlen)
    pressure[1:mid] .= 1.0
    pressure[(mid+1):end] .= 0.1
    g.u[:, 2] .= 0.
    g.u[:, 3] .= pressure ./ (g.gamma - 1)
    return
end

function fu(g::Grid)
    # return F(u)
    pressure = (g.gamma - 1) .* (g.u[:, 3] - 0.5 .* g.u[:, 2].^2 ./ (g.u[:, 1]))
    fu = zeros(Float64, g.xlen, 3)
    fu[:, 1] = g.u[:, 2]
    fu[:, 2] = g.u[:, 2].^2 ./ g.u[:, 1] .+ pressure
    fu[:, 3] = (g.u[:, 3] + pressure) .* g.u[:, 2] ./ g.u[:, 1]
    fu
end

function fill_BCs!(g::Grid)
    for i = 1:g.ng
        g.u[i, :] = g.u[g.jlo, :]
    end
    for i = (g.jhi + 1) : (g.jhi + g.ng)
        g.u[i, :] = g.u[g.jhi, :]
    end
    return
end

function summary(g::Grid)
    println("t = ", g.t)
    println("Size: ", size(g.u))
    println("Density (every 5th): ", g.u[:, 1][g.jlo:5:g.jhi])
    println("Velocity (every 5th): ", (g.u[:, 2] ./ g.u[:, 1])[g.jlo:5:g.jhi])
end

""" make the plot or update the animation """
function plot_curve(g::Grid; fn="t.png", is_save=true)
    # calculate rho, u, p e
    x = g.x[g.jlo:g.jhi]
    rho_f  = g.u[g.jlo:g.jhi, 1]
    vel_f = g.u[g.jlo:g.jhi, 2] ./ rho_f
    e_f  = g.u[g.jlo:g.jhi, 3]
    epsilon_f = (e_f./rho_f) - (.5 .* vel_f.^2)
    p = (g.gamma - 1) .* rho_f .* epsilon_f
    data = zeros(size(rho_f, 1), 4)
    data[:, 1] = rho_f
    data[:, 2] = p
    data[:, 3] = vel_f
    data[:, 4] = epsilon_f

    p = scatter(x, data, layout=4,
            # title=["density" "pressure" "velocity" "specific energy"],
            ms=1, legend=false, xlabel="x",
            ylabel=["rho" "p" "vel" "epsilon"], xlim=[0, 1],
            ylim=[(0., 1.1) (-0., 1.2) (-.2, 1) (1.6, 3.)], dpi=300)

    # annotate!(1.25, 1.25, "t = $(g.t)")
    if is_save
        savefig(fn)
    else
        return p
    end
end

""" make the plot or update the animation """
function plot_heatmap_and_curve(g::Grid; fn="t.png", is_save=true)
    # calculate rho, u, p e
    x = g.x[g.jlo:g.jhi]
    h = floor(Int16, size(x, 1) / 8)
    y = 1:h
    rho_f  = g.u[g.jlo:g.jhi, 1]
    z = float(ones(h) * reshape(rho_f, 1, :))
    p0 = heatmap(x, y, z, dpi=300, size=(600, 100), clim=(0., 1.), xlim=(-0.05, 1.05),
            showaxis = false, c=:thermal)
    p1 = plot_curve(g, is_save=false)
    l = @layout [a{0.2h} ; b]
    plot(p0, p1, layout=l)
    if is_save
        savefig(fn)
    end
end

""" the main function """
function runnit(dx, tmax; dnout::Int=1, anim::Any, plotit::Function)
    println("Hello, world!")
    g = Grid(nx=dx, ng=1, t=0.)
    v = 1.
    g.C = 0.45
    dt = g.C * g.dx / v
    init!(g)
    uinit = copy(g.u)
    unew = zeros(size(g.u))
    count = 0
    ocount = 0
    
    println("Init:")
    println("dt = ", dt, " t = ", g.t)
    # summary(g)
    # plot_curve(g, fn="f_init.png")
    # plot_heatmap_and_curve(g, fn=@sprintf("fig/f_%04d.png", ocount))
    skip = dnout
    while g.t < tmax + dt - 1e-10
        fill_BCs!(g)
        if g.t > tmax
            g.t = tmax
            dt = dt - (g.t - tmax)
        end
        count += 1
        ### LAX SCHEME ###
        for j in g.jlo:g.jhi
            unew[j, :] = 0.5 * (g.u[j - 1, :] + g.u[j + 1, :]) - 0.5 * dt / g.dx * (fu(g)[j + 1, :] - fu(g)[j - 1, :])
        end
        g.u = copy(unew)
        g.t = g.t + dt
        # summary(g)
        if count % skip == 0
            ocount += 1
            # plot_curve(g, fn=@sprintf("fig/f_%04d.png", idx))
            # plot_heatmap(g, fn=@sprintf("fig/f_%04d.png", ocount))
            # plot_heatmap(g, fn=@sprintf("fig/f_%04d.png", ocount), is_save=false)
            plotit(g, fn=@sprintf("fig/f_%04d.png", ocount), is_save=false)
            Plots.frame(anim)
            println("Frame ", ocount)
        end
    end
end 

Tend = 0.3
# Nd = 64
Nd = 512
Anim = Plots.Animation()
# runnit(Nd, Tend, dnout=5, anim=Anim, plotit=plot_curve)
runnit(Nd, Tend, dnout=5, anim=Anim, plotit=plot_heatmap_and_curve)
gif(Anim, "tube-10fps.gif", fps=10)
gif(Anim, "tube-15fps.gif", fps=15)
gif(Anim, "tube-20fps.gif", fps=20)