Process ASPECT Exhumation Model Outputs

This notebook demonstrates how to use GDTchron to predict AHe, AFT, and ZHe ages from the results of the accompanying ASPECT model of simplified exhumation defined in exhumation_box.prm and run by run_aspect_model.ipynb.

[1]:
# Imports
import os
import shutil

import cmcrameri.cm as cmc
import cv2
import matplotlib.cm as cm
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pyvista as pv
from IPython.display import Video
from tqdm import tqdm

import gdtchron as gdt

The cell below creates a list of 200 sorted files from the .pvtu files output by the ASPECT model

[2]:
# Get files from model output
part_path = 'output_exhumation_box/particles'
files = [
    os.path.join(part_path, file) for file in os.listdir(part_path)
    if file.endswith('.pvtu')
    ]
files.sort()

This cell feeds those files into GDTchron to predict AHe ages. The list of files, the thermochronologic system, the time between model outputs (Myr), the prefix for the file names, and the number of processes/cores to use are specified in this example.

Note that this and the other calls to gdt.run_vtk may take a couple hours each to run, depending on the system and number of processes.

[3]:
# Run AHe
gdt.run_vtk(files=files, system='AHe', time_interval=0.1,
            file_prefix='meshes_tchron_exhumation', processes=os.cpu_count() / 2)

All AHe timesteps complete

The cell below sets the input files to now be the output .vtu files containing the AHe ages, so that all three systems will be saved to the same set of files that are separate from the original model output

[4]:
# Set path to reuse files from AHe
new_path = 'meshes_tchron_exhumation'
files = [
    os.path.join(new_path, file) for file in os.listdir(new_path)
    if file.endswith('.vtu')
    ]
files.sort()

The next two cells below simply take the files with AHe ages and add AFT and ZHe. Note again that these may each take a couple hours to run.

[5]:
# Add AFT
gdt.run_vtk(files=files, system='AFT', time_interval=0.1,
            file_prefix='meshes_tchron_exhumation', processes=os.cpu_count() / 2)

All AFT timesteps complete
[6]:
# Add ZHe
gdt.run_vtk(files=files, system='ZHe', time_interval=0.1,
            file_prefix='meshes_tchron_exhumation', processes=os.cpu_count() / 2)

All ZHe timesteps complete

The next set of cells uses the final set of .vtu files with the three systems to make a video showing the evolution of thermochronologic ages over the model runtime. The cell below sets some parameters for the image files that will be used in the video.

[5]:
# Set up video
directory = 'meshes_tchron_exhumation'

time_total = 20  # Myr
model_step = 0.1  # Myr
bounds = [35, 65, 0, 10]  # km, X position and depth

nsteps = int(time_total / model_step)

timesteps = np.arange(0, nsteps + 1, 1)

files = [
    os.path.join(directory, file) for file in os.listdir(directory)
    if file.endswith('.vtu')
    ]
files.sort()

# Load solution files for tempterature contours
dir_temp = 'output_exhumation_box/solution'
files_t = [
    os.path.join(dir_temp, file) for file in os.listdir(dir_temp)
    if file.endswith('.pvtu')
    ]
files_t.sort()

image_dir = 'images/'

ahe_cmap = cmc.lapaz_r
aft_cmap = cmc.lajolla_r
zhe_cmap = cmc.bamako_r
cat_cmap = cmc.batlowS
clim = [0, time_total]
bar = False

The cell below generates the images that will be stitched together to make the video

[6]:
# Run image creation
try:
    shutil.rmtree(image_dir)
except Exception:
    print("Creating new image directory...")
else:
    print("Cleared existing image directory...")

os.makedirs(image_dir, exist_ok=False)

# Make images for the video
for k, step in enumerate(tqdm(timesteps)):
    time_ma = time_total - (step * model_step)
    time_str = str(round(step / 2, 1)).zfill(5).replace('.', '-')

    fig, axs = plt.subplots(2, 2, dpi=300, figsize=(6.5, 6))
    axs = axs.flat

    title = 'Uplift' if 5 <= time_ma <= 10 else 'Quiescence'

    axs[3].set_title(str(round(time_ma, 1)) + ' Ma - ' + title, loc='left')

    mesh = pv.read(files[k])
    mesh.points = mesh.points / 1e3  # Convert to km
    mesh.points[:, 1] = -(mesh.points[:, 1] - 20)  # Convert Y position to depth

    gdt.plot_vtk_2d(mesh, 'AHe', bounds=bounds, ax=axs[0],
              cmap=ahe_cmap, colorbar=bar, clim=clim)

    gdt.plot_vtk_2d(mesh, 'AFT', bounds=bounds, ax=axs[1],
            cmap=aft_cmap, colorbar=bar, clim=clim)

    gdt.plot_vtk_2d(mesh, 'ZHe', bounds=bounds, ax=axs[2],
        cmap=zhe_cmap, colorbar=bar, clim=clim)

    # Load solution file for temperature contours
    mesh_t = pv.read(files_t[k])
    mesh_t.points /= 1e3  # Convert to km
    mesh_t['T'] -= 273.15  # Convert temp to °C
    mesh_t.points[:, 1] = -(mesh_t.points[:, 1] - 20)  # Convert Y position to depth
    mesh_cntrs = mesh_t.contour(isosurfaces=np.arange(100, 301, 200), scalars='T')

    for ax in axs[0:3]:
        gdt.plot_vtk_2d(mesh_cntrs, None, bounds=bounds, ax=ax,
          color='dark grey', line_width=8, colorbar=True)

    axs[0].set_title('AHe')
    axs[1].set_title('AFT')
    axs[2].set_title('ZHe')

    y = np.round(mesh.points[:, 1], 0)
    AHe = mesh['AHe']
    AFT = mesh['AFT']
    ZHe = mesh['ZHe']

    df = pd.DataFrame({'y': y, 'AHe': AHe, 'AFT': AFT, 'ZHe': ZHe})
    df_max = df.groupby('y').agg({'y': 'first', 'AHe': 'max', 'AFT': 'max',
                                   'ZHe': 'max'})

    axs[3].plot(df_max['AHe'], df_max['y'], c=cat_cmap.colors[6], label='AHe')
    axs[3].plot(df_max['AFT'], df_max['y'], c=cat_cmap.colors[4], label='AFT')
    axs[3].plot(df_max['ZHe'], df_max['y'], c=cat_cmap.colors[2], label='ZHe')

    axs[3].set_xlim(-0.25, time_total)
    axs[3].set_ylim(bounds[2], bounds[3])
    axs[3].set_xlabel('Maximum Age (Ma)', fontsize=6)
    axs[3].tick_params(labelsize=6)
    axs[3].text(0.5, 0.2,
                'Surface Ages:\n'
                  'AHe: ' + str(round(df_max['AHe'][0], 1)) + ' Ma\n'
                  'AFT: ' + str(round(df_max['AFT'][0], 1)) + ' Ma\n'
                  'ZHe: ' + str(round(df_max['ZHe'][0], 1)) + ' Ma\n',
                transform=axs[3].transAxes)
    axs[3].legend(fontsize=6, loc='lower right')

    for ax in [axs[0], axs[1], axs[2]]:
        ax.set_xlabel('X Position (km)', fontsize=6)

    for ax in axs:
        ax.set_ylabel('Depth (km)', fontsize=6, labelpad=2)
        ax.tick_params(labelsize=6)
        ax.invert_yaxis()

    cax_ahe = fig.add_axes([0.24, 0.68, 0.1, 0.015])
    cax_aft = fig.add_axes([0.7, 0.68, 0.1, 0.015])
    cax_zhe = fig.add_axes([0.24, 0.26, 0.1, 0.015])

    norm = mcolors.Normalize(vmin=clim[0], vmax=clim[1])

    sm_ahe = cm.ScalarMappable(cmap=ahe_cmap, norm=norm)
    sm_aft = cm.ScalarMappable(cmap=aft_cmap, norm=norm)
    sm_zhe = cm.ScalarMappable(cmap=zhe_cmap, norm=norm)
    sms = [sm_ahe, sm_aft, sm_zhe]

    for k, cax in enumerate([cax_ahe, cax_aft, cax_zhe]):
        cax.tick_params(labelsize=6, pad=2)
        plt.colorbar(sms[k], cax=cax, orientation='horizontal')
        cax.set_title('Age (Ma)', fontsize=6, pad=2)

    fig.savefig(image_dir + time_str + '.jpg')
    plt.close()
Cleared existing image directory...
100%|██████████| 201/201 [03:03<00:00,  1.10it/s]

The cell below stitches the images together and makes a video file: video_tchron.mp4.

[7]:
# Make movie
img_paths = [
    image_dir + file for file in sorted(os.listdir(image_dir)) if file.endswith('.jpg')
    ]

frame = cv2.imread(img_paths[0])
height, width, layers = frame.shape

fourcc = cv2.VideoWriter_fourcc(*'avc1')

frate = 1 / model_step

video = cv2.VideoWriter('video_tchron.mp4', fourcc, frate, (width, height))

for img in img_paths:
    video.write(cv2.imread(img))

cv2.destroyAllWindows()
video.release()

This final cell displays the video in this Jupyter Notebook.

[8]:
video_path = 'video_tchron.mp4'
Video(video_path, embed=True, width=500)
[8]: