Skip to content

Animations

Animations provide an effective way to bring time-dependent data to life, making it easier to observe and analyze dynamic changes that unfold over time. This guide demonstrates how to create animations using MicPy and Matplotlib.

We will begin by animating the evolution of a carbon concentration field. Next, we will visualize the phase fractions over temperature. Finally, we will combine these concepts to create a side-by-side animation showing spatial phase distributions alongside a line plot of phase fractions.

Example 1: Animation of a Concentration Field

In our first example, we use the .conc1 file from the A001_Delta_Gamma example to visualize the dynamic variations in carbon concentration during the processes of solidification and delta-gamma phase transformation.

The video below shows the resulting animation, where the color coding represents the carbon concentration at each point in the domain. The time is displayed in the title, indicating the current simulation step. You can pause or resume the animation by clicking on the video.

Note

While the examples in this guide are based on the MICRESS A001_Delta_Gamma simulation, a few adjustments were made to simplify the data processing and improve the visualization:

  1. Fixed Spatial Reference: The moving frame mechanism was disabled to maintain a consistent spatial reference frame.
  2. Streamlined Phase Data Processing: Interface output in the phase field files was turned off to facilitate the extraction of phase information from the simulation outputs.
  3. Enhanced Temporal Resolution: The simulation was configured to produce output at higher temporal intervals, resulting in smoother transitions.

You can download the modified input and output files from the MicPy examples repository.

Step 1: Import Required Libraries

We begin by importing the necessary libraries. MicPy handles the data from the .conc file, and Matplotlib is used to create the animation.

from micpy import bin
from matplotlib.animation import FuncAnimation

Step 2: Load the Concentration Field Data

The concentration field data is loaded using MicPy's bin.File interface. The .conc1 file contains the simulation output data for carbon concentration at different time steps.

# Open the .conc1 file and load the concentration data
with bin.File("A001_Delta_Gamma.conc1") as f:
    conc = f.read()

Step 3: Create the Initial Plot

We create a Matplotlib figure and plot the concentration field for the first time step. This provides a baseline for the animation.

# Plot the initial concentration field
fig, ax = bin.plot(conc.get_field(0))

Step 4: Reference the Mesh Object

The mesh object holds the graphical representation of the concentration field. To ensure consistent color mapping across all frames, we set the minimum and maximum values of the data range explicitly.

# Access the mesh object and set consistent color mapping
mesh = ax.collections[0]
mesh.norm.vmin = conc.min()
mesh.norm.vmax = conc.max()

Step 5: Define the Update Function

The update function modifies the mesh object for each frame of the animation. It retrieves the concentration field for the current time step, updates the mesh data, and adjusts the plot title to display the simulation time.

# Define the update function for the animation
def update(frame):
    # Get the concentration field for the current frame
    field = conc.get_field(frame)
    # Update the mesh with the new data
    mesh.set_array(field.flatten())
    # Update the plot title with the current time
    ax.set_title(f"Time: {field.time:.2f} s")
    return [mesh]

Step 6: Optimize Plot Layout

Proper layout adjustments ensure the plot dimensions fit well and avoid cropping or scaling issues in the animation.

# Adjust the plot layout to fit properly
fig.set_tight_layout({'pad': 1})
box = fig.get_tightbbox(fig.canvas.get_renderer())
fig.set_size_inches(box.width, box.height)
fig.set_tight_layout(False)

Step 7: Create and Save the Animation

Finally, we use Matplotlib's FuncAnimation class to generate the animation. The animation is saved in WebM format with a high frame rate for smooth playback.

# Create the animation
frames = conc.shape[0] # Number of time steps
ani = FuncAnimation(fig, update, frames=frames)

# Save the animation as a WebM file
ani.save("animation-1.webm", fps=30, codec="libvpx-vp9")

Example 2: Animation of Phase Fractions

In this example, we create an animation to visualize the evolution of phase fractions over temperature during the simulation. The .TabF file contains the temperature and phase fraction data for liquid, BCC_A2 ferrite, and FCC_A1 austenite. By animating this data, we can observe the progression of phase transformations over time.

The animation below shows the evolution of the phase fractions as the temperature decreases. The x-axis represents temperature, and the y-axis represents the fraction of each phase. You can play or pause the animation by clicking on the video.

Step 1: Import Required Libraries

We start by importing the necessary libraries for reading the .TabF file and creating the animation.

from micpy import tab
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt

Step 2: Load Phase Fraction Data

The .TabF file is read using the tab.read() function. We extract the temperature and phase fraction columns and rename them for clarity.

# Read the .TabF file
df = tab.read("A001_Delta_Gamma.TabF")

# Extract temperature and phase fractions
temperature = df["Temperature [K]"]
fractions = df[
    ["Fraction Phase 0 LIQUID", "Fraction Phase 1 BCC_A2", "Fraction Phase 2 FCC_A1"]
]

# Rename columns for clarity
fractions.columns = ["Liquid", "BCC_A2", "FCC_A1"]

Step 3: Create the Plot

We initialize the Matplotlib figure and axes. Empty lines are added for each phase, which will be updated during the animation.

# Create the figure and axes
fig, ax = plt.subplots()

# Initialize lines for each phase
lines = []
for phase in fractions.columns:
    line, = ax.plot([], [], label=phase) # Empty line for each phase
    lines.append(line)

Step 4: Define the Update Function

The update function modifies the lines for each frame of the animation. It sets the x and y data for each phase and updates the plot title to show the current temperature.

def update(frame):
    # Update the data for each line
    for line, phase in zip(lines, fractions.columns):
        line.set_data(temperature[:frame+1], fractions[phase][:frame+1])

    # Update the plot title
    ax.set_title(f"Temperature: {temperature.iloc[frame]:.2f} K")
    return lines

Step 5: Configure the Axes

We configure the plot's axes to ensure proper visualization.

# Set axis limits
ax.set_xlim(temperature.min(), temperature.max())
ax.set_ylim(0, 1)

# Set axis labels
ax.set_xlabel("Temperature [K]")
ax.set_ylabel("Fraction")

# Invert x-axis for decreasing temperature during solidification
ax.invert_xaxis()

# Add a legend
legend = ax.legend()

Step 6: Create and Save the Animation

Using Matplotlib's FuncAnimation, we generate the animation by updating the plot for each frame. The animation is saved in WebM format for wide compatibility and efficient compression.

# Create the animation
ani = FuncAnimation(fig, update, frames=len(df))

# Save the animation
ani.save("animation-2.webm", fps=60, codec="libvpx-vp9")

Example 3: Side-by-Side Animation of Spatial Phase Evolution and Phase Fractions

In this example, we create a side-by-side animation that combines spatial phase evolution with phase fraction data over time. The left panel shows an animated pseudocolor plot of the spatial phase distribution, while the right panel displays an animated line plot of the phase fractions over time. The fractions are calculated directly from the phase field data using NumPy.

The resulting animation provides a comprehensive view of the phase transformations, enabling a better understanding of the spatial and temporal evolution of the system. Clicking on the video will start or pause the animation.

Step 1: Import Required Libraries

We start by importing the necessary libraries for handling MICRESS output data, creating animations, and performing numerical calculations.

from micpy import bin
from matplotlib.animation import FuncAnimation
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import numpy as np

Step 2: Load Phase Field Data

The .phas file is read using MicPy's bin.File interface, which efficiently handles large binary files. We use a chunk_size to optimize reading performance.

with bin.File("A001_Delta_Gamma.phas", chunk_size=1024) as f:
    phases = f.read()

Step 3: Define Phase Labels, Colors, and Colormap

We define the phase labels, colors, and colormap for visualizing the phases. Each phase is assigned a unique color.

# Define labels and colors for phases
labels = ["Liquid", "BCC_A2", "FCC_A1"]
colors = ["blue", "green", "red"]

# Create a colormap with the defined colors
cmap = ListedColormap(colors)

Step 4: Create the Plot Layout

We create a side-by-side layout with two subplots:

  • Left Panel: Animated pseudocolor plot of spatial phase distribution.
  • Right Panel: Line plot showing phase fractions over time.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 6))

Step 5: Initialize the Pseudocolor Plot

We plot the initial phase field on the left panel using the bin.plot() function with the custom colormap. The mesh object is used to update the plot during the animation.

# Plot the initial phase field
args = bin.PlotArgs(ax=ax1, cmap=cmap, vmin=phases.min(), vmax=phases.max())
fig, ax1 = bin.plot(phases.get_field(0), args=args)

# Access the mesh object for updates
mesh = ax1.collections[0]

Step 6: Initialize the Line Plot

We initialize the right panel with empty lines for each phase. These lines will be updated to show the phase fractions over time.

# Initialize the line plot for phase fractions
lines = []
for label, color in zip(labels, colors):
    (line,) = ax.plot([], [], label=label, color=color)
    lines.append(line)

# Configure the right panel axes
ax2.set_xlim(phases.get_field(0).time, phases.get_field(-1).time)
ax2.set_ylim(0, 1)
ax2.set_xlabel("Time [s]")
ax2.set_ylabel("Fraction")
legend = ax2.legend(loc="upper right")

Step 7: Define the Update Function

The update function is the core of the animation. It updates both the pseudocolor plot and the line plot for each frame:

  1. Updates the phase field on the left panel.
  2. Calculates phase fractions using NumPy for the right panel.
def update(frame):
    # Update the pseudocolor plot
    field = phases.get_field(frame)
    mesh.set_array(field.flatten())
    ax1.set_title(f"Time: {field.time:.2f} s")

    # Calculate and update phase fractions
    for phase in np.unique(field): # Iterate over all unique phase values
        # Calculate the fraction of the domain occupied by the phase
        fraction = np.sum(field == phase) / field.size

        # Update the corresponding line data
        line = lines[phase]
        line.set_data(
            np.append(list(line.get_xdata()), time),
            np.append(list(line.get_ydata()), fraction)
        )

    return [mesh, *lines]

Step 8: Optimize Plot Layout

We adjust the layout to prevent overlapping and ensure proper sizing of the subplots.

# Optimize the plot layout
fig.set_tight_layout({"pad": 1})
box = fig.get_tightbbox(fig.canvas.get_renderer())
fig.set_size_inches(box.width, box.height)
fig.set_tight_layout(False)

Step 9: Create and Save the Animation

Finally, we use FuncAnimation to create the animation. The frames parameter specifies the total number of time steps.

# Create the animation
frames = phases.shape[0] # Number of time steps
animation = FuncAnimation(fig, update, frames=frames)

# Save the animation to a file
animation.save("animation-3.webm", fps=60, codec="libvpx-vp9")