Chart animations#
Render an animated chart of a simulation as a video, then upload it next to the simulation’s channels so you can replay them together. This example simulates a double pendulum.
The examples also need pip install scipy matplotlib "imageio[ffmpeg]".
Connect the client#
This guide assumes you have installed the client and setup your API keys.
from nominal.core import NominalClient
client = NominalClient.from_profile("default")
Simulate and save the channels#
Solve the equations of motion once, and save the channels with absolute timestamps:
from datetime import datetime, timedelta, timezone
import numpy as np
import pandas as pd
from scipy.integrate import solve_ivp
g, L = 9.81, 1.0 # both arms 1 m long, both masses 1 kg
def pendulum(t, y):
theta1, z1, theta2, z2 = y
d = theta2 - theta1
den = 2 * L - L * np.cos(d) ** 2
dz1 = (L * z1**2 * np.sin(d) * np.cos(d) + g * np.sin(theta2) * np.cos(d) + L * z2**2 * np.sin(d) - 2 * g * np.sin(theta1)) / den
dz2 = (-L * z2**2 * np.sin(d) * np.cos(d) + 2 * (g * np.sin(theta1) * np.cos(d) - L * z1**2 * np.sin(d) - g * np.sin(theta2))) / den
return [z1, dz1, z2, dz2]
t = np.linspace(0, 25, 500) # 25 s at 20 frames per second
theta1, _, theta2, _ = solve_ivp(pendulum, (0, 25), [np.pi / 2, 0, np.pi / 2, 0], t_eval=t).y
x1, y1 = L * np.sin(theta1), -L * np.cos(theta1)
x2, y2 = x1 + L * np.sin(theta2), y1 - L * np.cos(theta2)
start = datetime.now(timezone.utc)
pd.DataFrame(
{
"time": start.timestamp() + t,
"height_1": y1 + 2 * L,
"height_2": y2 + 2 * L,
"speed_2": np.hypot(np.gradient(x2, t), np.gradient(y2, t)),
}
).to_csv("pendulum.csv", index=False)
Render the video#
Draw one frame per time step and write them to an MP4 with imageio:
import imageio.v2 as imageio
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 6))
with imageio.get_writer("pendulum.mp4", fps=20) as writer:
for i in range(len(t)):
ax.clear()
ax.set(xlim=(-2, 2), ylim=(-2, 2))
ax.plot([0, x1[i], x2[i]], [0, y1[i], y2[i]], "o-")
fig.canvas.draw()
writer.append_data(np.asarray(fig.canvas.buffer_rgba())[..., :3])
The frame rate matches the simulation’s time step, so the video lasts as long as the simulation.
Upload the channels and the video#
Upload both to one dataset. The video starts at the simulation’s first timestamp:
dataset = client.create_dataset("Double pendulum")
dataset.add_tabular_data("pendulum.csv", timestamp_column="time", timestamp_type="epoch_seconds").poll_until_ingestion_completed()
dataset.add_video("pendulum.mp4", channel="pendulum_video", start=start).poll_until_ingestion_completed()
run = client.create_run("Double pendulum", start=start, end=start + timedelta(seconds=25))
run.add_dataset("simulation", dataset)
print(run.nominal_url)
If the video drifts from the channels, change its start with update(starting_timestamp=...) on the file from list_video_files().
See Video ingest for other options.
Replay it in a workbook#
In a workbook on the run, add a video panel for pendulum_video and a chart of height_1 and height_2.
Playing the video moves the chart’s cursor with it.