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:
- Fixed Spatial Reference: The moving frame mechanism was disabled to maintain a consistent spatial reference frame.
- 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.
- 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.
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.
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.
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.
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:
- Updates the phase field on the left panel.
- 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.