Menu

postProcess/video.py

# Author: Vatsal Sanjay
# [email protected]
# Physics of Fluids
# Last updated: Dec 24, 2024

import numpy as np
import os
import subprocess as sp
import matplotlib
matplotlib.use('Agg')  # Use non-interactive backend
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.ticker import StrMethodFormatter
import multiprocessing as mp
from functools import partial
import argparse

import matplotlib.colors as mcolors
custom_colors = ["white", "#DA8A67", "#A0522D", "#400000"]
custom_cmap = mcolors.LinearSegmentedColormap.from_list("custom_hot", custom_colors)

# Set up matplotlib configuration
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'
plt.rcParams['text.latex.preamble'] = r'\usepackage{amsmath}\usepackage{amssymb}'
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['font.serif'] = ['Computer Modern Roman'] + plt.rcParams['font.serif']

def gettingFacets(filename):
    exe = ["./getFacets", filename]
    p = sp.Popen(exe, stdout=sp.PIPE, stderr=sp.PIPE)
    stdout, stderr = p.communicate()
    temp1 = stderr.decode("utf-8")
    temp2 = temp1.split("\n")
    segs = []
    skip = False
    if (len(temp2) > 1e2):
        for n1 in range(len(temp2)):
            temp3 = temp2[n1].split(" ")
            if temp3 == ['']:
                skip = False
                pass
            else:
                if not skip:
                    temp4 = temp2[n1+1].split(" ")
                    r1, z1 = np.array([float(temp3[1]), float(temp3[0])])
                    r2, z2 = np.array([float(temp4[1]), float(temp4[0])])
                    segs.append(((r1, z1),(r2, z2)))
                    segs.append(((-r1, z1),(-r2, z2)))
                    skip = True
    return segs

def gettingfield(filename, zmin, rmin, zmax, rmax, nr):
    exe = ["./getData", filename, str(zmin), str(rmin), str(zmax), str(rmax), str(nr)]
    p = sp.Popen(exe, stdout=sp.PIPE, stderr=sp.PIPE)
    stdout, stderr = p.communicate()
    temp1 = stderr.decode("utf-8")
    temp2 = temp1.split("\n")
    # print(temp2) #debugging
    Rtemp, Ztemp, D2temp, veltemp  = [],[],[],[]

    for n1 in range(len(temp2)):
        temp3 = temp2[n1].split(" ")
        if temp3 == ['']:
            pass
        else:
            Ztemp.append(float(temp3[0]))
            Rtemp.append(float(temp3[1]))
            D2temp.append(float(temp3[2]))
            veltemp.append(float(temp3[3]))


    R = np.asarray(Rtemp)
    Z = np.asarray(Ztemp)
    D2 = np.asarray(D2temp)
    vel = np.asarray(veltemp)

    nz = int(len(Z)/nr)

    # print("nr is %d %d" % (nr, len(R))) # debugging
    print("nz is %d" % nz)

    R.resize((nz, nr))
    Z.resize((nz, nr))
    D2.resize((nz, nr))
    vel.resize((nz, nr))

    return R, Z, D2, vel, nz

# ----------------------------------------------------------------------------------------------------------------------

def process_timestep(ti, caseToProcess, folder, nGFS, GridsPerR, rmin, rmax, zmin, zmax, lw):
    t = 0.01 * ti
    place = f"{caseToProcess}/intermediate/snapshot-{t:.4f}"
    name = f"{folder}/{int(t*1000):08d}.png"

    if not os.path.exists(place):
        print(f"{place} File not found!")
        return

    if os.path.exists(name):
        print(f"{name} Image present!")
        return

    # --- Gather interface segments and field data as before ---
    segs = gettingFacets(place)
    nr = int(GridsPerR * rmax)
    R, Z, D2, vel, nz = gettingfield(place, zmin, rmin, zmax, rmax, nr)


    [xminp, xmaxp, yminp, ymaxp] = R.min(), R.max(), Z.min(), Z.max()
    extent_vel = [-xminp, -xmaxp, yminp, ymaxp]
    extent_D2 = [xminp, xmaxp, yminp, ymaxp]

    AxesLabel, TickLabel = 30, 20
    fig, ax = plt.subplots(1, 1, figsize=(19.20, 10.80))


    ax.plot([-rmax, rmax], [0, 0], '--', color='grey', linewidth=lw)  # "horizontal" axis is z
    ax.plot([0, 0], [zmin, zmax], '-.', color='grey', linewidth=lw)  # "vertical" axis is r

    # Domain box:
    ax.plot([-rmax, rmax], [zmin, zmin], '-', color='black', linewidth=lw)
    ax.plot([-rmax, rmax], [zmax, zmax], '-', color='black', linewidth=lw)
    ax.plot([-rmax, -rmax], [zmin, zmax], '-', color='black', linewidth=lw)
    ax.plot([rmax, rmax], [zmin, zmax], '-', color='black', linewidth=lw)

    line_segments = LineCollection(segs, linewidths=4, colors='green')
    ax.add_collection(line_segments)

    # ----------------------------------------------------------
    # Now show imshow with the rotated arrays and extents:
    # ----------------------------------------------------------
    cntrl1 = ax.imshow(
        vel, 
        cmap="Blues", 
        interpolation='bilinear', 
        origin='lower', 
        extent=extent_vel,
        vmin=0.0,
        vmax=4e0
    )
    cntrl2 = ax.imshow(
        D2, 
        cmap="hot_r", 
        interpolation='bilinear', 
        origin='lower', 
        extent=extent_D2,
        vmin=-3e0,
        vmax=2e0
    )

    # Equal aspect ensures squares in the new orientation
    ax.set_aspect('equal')
    ax.set_xlim(-rmax, rmax)  # x range
    ax.set_ylim(zmin, zmax)  # y range

    # Titles and labels that match the new orientation
    ax.set_title(fr'$t/\tau_{{\gamma}} = {t:.4f}$', fontsize=TickLabel)

    # Colorbars: place them closer to the plot edges
    fig.subplots_adjust(left=0.1, right=0.9)  # adjust spacing for vertical colorbars
    
    # Left colorbar
    cbar_ax1 = fig.add_axes([0.07, 0.15, 0.02, 0.7])   # x,y,width,height in figure coords
    c1 = plt.colorbar(cntrl1, cax=cbar_ax1, orientation='vertical')
    c1.ax.tick_params(labelsize=TickLabel)
    c1.set_label(r'$\|u_i\|/V_{\gamma}$', fontsize=AxesLabel, labelpad=10, rotation=90, position=(0,0.5))
    c1.ax.yaxis.set_label_position('left')
    c1.ax.yaxis.set_ticks_position('left')  # Move ticks to left side
    c1.ax.tick_params(axis='y', labelright=False, labelleft=True)  # Move tick labels to left side

    # Right colorbar
    cbar_ax2 = fig.add_axes([0.92, 0.15, 0.02, 0.7])
    c2 = plt.colorbar(cntrl2, cax=cbar_ax2, orientation='vertical')
    c2.ax.tick_params(labelsize=TickLabel)
    c2.set_label(r'$\|\mathcal{D}_{ij}\|\tau_{\gamma}$', fontsize=AxesLabel, labelpad=10)

    ax.axis('off')

    # Save with higher DPI and specific backend
    plt.savefig(name, bbox_inches="tight", dpi=150, backend='agg')
    plt.close()

def main():
    # Get number of CPUs from command line argument, or use all available
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('--CPUs', type=int, default=mp.cpu_count(), help='Number of CPUs to use')
    parser.add_argument('--nGFS', type=int, default=256, help='Number of restart files to process')

    parser.add_argument('--ZMAX', type=float, default=1.0, help='Maximum Z value')
    parser.add_argument('--RMAX', type=float, default=4.0, help='Maximum R value')
    parser.add_argument('--ZMIN', type=float, default=-3.0, help='Minimum Z value')
    parser.add_argument('--RMIN', type=float, default=0.0, help='Minimum R value')
    parser.add_argument('--caseToProcess', type=str, default='../testCases/burstingBubbleHB', help='Case to process')
    
    args = parser.parse_args()

    CPUStoUse = args.CPUs
    nGFS = args.nGFS
    ZMAX = args.ZMAX
    RMAX = args.RMAX
    ZMIN = args.ZMIN
    RMIN = args.RMIN
    caseToProcess = args.caseToProcess
    num_processes = CPUStoUse
    
    rmin, rmax, zmin, zmax = [RMIN, RMAX, ZMIN, ZMAX]
    GridsPerR = 128

    lw = 2
    folder = 'Video'

    if not os.path.isdir(folder):
        os.makedirs(folder)

    # Create a pool of worker processes
    with mp.Pool(processes=num_processes) as pool:
        # Create partial function with fixed arguments
        process_func = partial(process_timestep, caseToProcess=caseToProcess, 
                             folder=folder, nGFS=nGFS,
                             GridsPerR=GridsPerR, rmin=rmin, rmax=rmax, 
                             zmin=zmin, zmax=zmax, lw=lw)
        # Map the process_func to all timesteps
        pool.map(process_func, range(nGFS))

if __name__ == "__main__":
    main()