2D contour plots with multi-processing

Author Update time

This demo shows how to generate 2D colored contours with multi-processing. To run on a single node,

julia -p $ncores demo_2d_mp_pyplot.jl
using Distributed, ParallelDataTransfer
@everywhere using Vlasiator, VlasiatorPyPlot, Printf, LaTeXStrings

@everywhere function init_figure(cmaps, norms, ticks, pArgs1, extent)
   fig, axs = plt.subplots(2, 3; num=myid(),
      figsize=(13.5, 9.5), sharex=true, sharey=true, constrained_layout=true)

   for ax in axs
      ax.set_aspect("equal")
      # Set border line widths
      for loc in ("left", "bottom", "right", "top")
         edge = get(ax.spines, loc, nothing)
         edge.set_linewidth(2.0)
      end
      ax.xaxis.set_tick_params(width=2.0, length=3)
      ax.yaxis.set_tick_params(width=2.0, length=3)
      ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
      ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
   end

   for ax in axs[end,:]
      ax.set_xlabel(pArgs1.strx; fontsize)
   end
   for ax in axs[:,1]
      ax.set_ylabel(pArgs1.stry; fontsize)
   end

   titles = ("Density", "Pth", "Vx", "Bz", "Vz", "E")

   for (ax, title) in zip(axs, titles)
      ax.set_title(title; fontsize)
   end

   x1, x2 = Vlasiator.get_axis(pArgs1)

   range1 = searchsortedfirst(x1, extent[1]):searchsortedlast(x1, extent[2])
   range2 = searchsortedfirst(x2, extent[3]):searchsortedlast(x2, extent[4])

   fakedata = zeros(Float32, length(range2), length(range1))

   c1 = axs[1,1].pcolormesh(x1[range1], x2[range2], fakedata;
      norm=norms[1], cmap=cmaps[1])
   c2 = axs[1,2].pcolormesh(x1[range1], x2[range2], fakedata;
      norm=norms[2], cmap=cmaps[2])
   c3 = axs[1,3].pcolormesh(x1[range1], x2[range2], fakedata;
      norm=norms[3], cmap=cmaps[2])
   c4 = axs[2,1].pcolormesh(x1[range1], x2[range2], fakedata;
      norm=norms[4], cmap=cmaps[1])
   c5 = axs[2,2].pcolormesh(x1[range1], x2[range2], fakedata;
      norm=norms[5], cmap=cmaps[2])
   c6 = axs[2,3].pcolormesh(x1[range1], x2[range2], fakedata;
      norm=norms[6], cmap=cmaps[1])

   cs = (c1, c2, c3, c4, c5, c6)

   format = matplotlib.ticker.FormatStrFormatter("%.1f")

   cb1 = colorbar(c1; ax=axs[1,1], ticks=ticks[1], format)
   cb2 = colorbar(c2; ax=axs[1,2], ticks=ticks[2])
   cb3 = colorbar(c3; ax=axs[1,3], ticks=ticks[3])
   cb4 = colorbar(c4; ax=axs[2,1], ticks=ticks[4], format)
   cb5 = colorbar(c5; ax=axs[2,2], ticks=ticks[5], format, extend="both")
   cb6 = colorbar(c6; ax=axs[2,3], ticks=ticks[6], extend="max")

   ylabels = ("[amu/cc]", "[km/s]", "[km/s]", "[nPa]", "[nT]", "[mV/m]")

   for (i, cb) in enumerate((cb1, cb2, cb3, cb4, cb5, cb6))
      cb.ax.set_ylabel(ylabels[i]; fontsize)
      cb.outline.set_linewidth(1.0)
   end

   return fig, cs, range1, range2
end

@everywhere function update_plot!(fig, cs, range1, range2, outdir, file)
   isfile(outdir*file[end-8:end-5]*".png") && return

   println("file = $file")
   meta = load(file)

   data = Vlasiator.prep2d(meta, "proton/vg_rho", :mag)'
   cs[1].set_array(data[range2,range1] ./ 1f6)

   data = Vlasiator.prep2d(meta, "proton/vg_v", :x)'
   cs[2].set_array(data[range2,range1] ./ 1f3)

   data = Vlasiator.prep2d(meta, "proton/vg_v", :z)'
   cs[3].set_array(data[range2,range1] ./ 1f3)

   data = Vlasiator.prep2d(meta, "vg_pressure", :mag)'
   cs[4].set_array(data[range2,range1] .* 1f9)

   data = Vlasiator.prep2d(meta, "vg_b_vol", :z)'
   cs[5].set_array(data[range2,range1] .* 1f9)

   data = Vlasiator.prep2d(meta, "vg_e_vol", :mag)'
   cs[6].set_array(data[range2,range1] .* 1f3)

   str_title = @sprintf "Density pulse run, t= %4.1fs" meta.time
   fig.suptitle(str_title; fontsize="xx-large")

   savefig(outdir*file[end-8:end-5]*".png", bbox_inches="tight")
end

function make_jobs(files)
   for f in files
       put!(jobs, f)
   end
end

@everywhere function do_work(jobs, status, outdir, cmaps, norms, ticks, pArgs1, extent)
   fig, cs, range1, range2 = init_figure(cmaps, norms, ticks, pArgs1, extent)
   while true
      file = take!(jobs)
      update_plot!(fig, cs, range1, range2, outdir, file)
      put!(status, true)
   end
   close(fig)
end

################################################################################
files = filter(contains(r"^bulk.*\.vlsv$"), readdir())

nfile = length(files)
# Set output directory
const outdir = "contour/"

# Set colormaps for continuous and divergent data
const cmaps = (matplotlib.cm.turbo, matplotlib.cm.RdBu_r)
const axisunit = EARTH
const extent = [0., 20., -20., 20.] # [RE], default: [-Inf32, Inf32, -Inf32, Inf32]

# Plotting range for each variable
const ρmin, ρmax   = 0.1, 15.0     # [amu/cc]
const vxmin, vxmax = -650.0, 650.0 # [km/s]
const vzmin, vzmax = -500.0, 500.0 # [km/s]
const pmin, pmax   = 0.0, 3.6      # [nPa]
const bmin, bmax   = -60.0, 60.    # [nT]
const emin, emax   = 0.0, 20.      # [mV/m]

meta = load(files[1])

pArgs1 = Vlasiator.set_args(meta, "proton/vg_rho", axisunit; normal=:none)

norm1, ticks1 = set_colorbar(Linear, ρmin, ρmax)
norm2, ticks2 = set_colorbar(Linear, vxmin, vxmax)
norm3, ticks3 = set_colorbar(Linear, vzmin, vzmax)
norm4, ticks4 = set_colorbar(Linear, pmin, pmax)
norm5, ticks5 = set_colorbar(Linear, bmin, bmax)
norm6, ticks6 = set_colorbar(Linear, emin, emax)

const norms = (norm1, norm2, norm3, norm4, norm5, norm6)
const ticks = (ticks1, ticks2, ticks3, ticks4, ticks5, ticks6)

const jobs   = RemoteChannel(()->Channel{String}(nfile))
const status = RemoteChannel(()->Channel{Bool}(nworkers()))

@broadcast begin
   const fontsize = "x-large"
end

println("Total number of files: $nfile")
println("Running with $(nworkers()) workers...")

@async make_jobs(files) # Feed the jobs channel with all files to process.

@sync for p in workers()
   @async remote_do(do_work, p, jobs, status, outdir, cmaps, norms, ticks, pArgs1, extent)
end

let n = nfile
   t = @elapsed while n > 0 # wait for all jobs to complete
      take!(status)
      n -= 1
   end
   println("Finished in $(round(t, digits=2))s.")
end

This page was generated using DemoCards.jl.