import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import matplotlib
matplotlib.rcParams['animation.embed_limit'] = 60
from scipy.integrate import odeint
# Lorenz attractor parameters
sigma = 10.0
rho = 28.0
beta = 8.0 / 3.0
def lorenz_system(state, t):
"""
Lorenz attractor differential equations.
dx/dt = σ(y - x)
dy/dt = x(ρ - z) - y
dz/dt = xy - βz
"""
x, y, z = state
dx_dt = sigma * (y - x)
dy_dt = x * (rho - z) - y
dz_dt = x * y - beta * z
return [dx_dt, dy_dt, dz_dt]
# Generate 10 different initial conditions
np.random.seed(42)
n_trajectories = 10
initial_conditions = []
for i in range(n_trajectories):
# Small random perturbations around (0, 1, 1.05)
ic = [np.random.uniform(-1, 1),
np.random.uniform(0, 2),
np.random.uniform(20, 25)]
initial_conditions.append(ic)
# Time array
t = np.linspace(0, 50, 5000)
dt_per_point = t[1] - t[0]
# Solve ODE for all initial conditions
# print("Computing Lorenz attractor trajectories...")
all_trajectories = []
for i, ic in enumerate(initial_conditions):
trajectory = odeint(lorenz_system, ic, t)
all_trajectories.append(trajectory)
# print(f"Computed trajectory {i+1}/{n_trajectories}")
# Create figure with 3D axis
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')
# Set dark background
fig.patch.set_facecolor('#222222')
ax.set_facecolor('#222222')
# Make panes transparent
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False
# Make pane edges invisible
ax.xaxis.pane.set_edgecolor('none')
ax.yaxis.pane.set_edgecolor('none')
ax.zaxis.pane.set_edgecolor('none')
# Colors for different trajectories using viridis colormap
cmap = plt.get_cmap('viridis')
colors = [cmap(i / n_trajectories) for i in range(n_trajectories)]
# Initialize lines and points for each trajectory
lines = []
points = []
for i in range(n_trajectories):
line, = ax.plot([], [], [], '-', linewidth=2, alpha=0.8, color=colors[i])
point, = ax.plot([], [], [], 'o', markersize=3, color=colors[i])
lines.append(line)
points.append(point)
# Find global limits across all trajectories
all_x = np.concatenate([traj[:, 0] for traj in all_trajectories])
all_y = np.concatenate([traj[:, 1] for traj in all_trajectories])
all_z = np.concatenate([traj[:, 2] for traj in all_trajectories])
x_min, x_max = all_x.min(), all_x.max()
y_min, y_max = all_y.min(), all_y.max()
z_min, z_max = all_z.min(), all_z.max()
# Set symmetric limits around center
x_center = (x_max + x_min) / 2
y_center = (y_max + y_min) / 2
z_center = (z_max + z_min) / 2
x_range = max(abs(x_max - x_center), abs(x_min - x_center))
y_range = max(abs(y_max - y_center), abs(y_min - y_center))
z_range = max(abs(z_max - z_center), abs(z_min - z_center))
max_range = max(x_range, y_range, z_range) * 1.1
ax.set_xlim(x_center - max_range, x_center + max_range)
ax.set_ylim(y_center - max_range, y_center + max_range)
ax.set_zlim(z_center - max_range, z_center + max_range)
# Remove all ticks
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
# Remove grid
ax.grid(False)
# Hide the default axes completely
ax.xaxis.line.set_color('none')
ax.yaxis.line.set_color('none')
ax.zaxis.line.set_color('none')
# Hide axis labels from default axes
ax.set_xlabel('')
ax.set_ylabel('')
ax.set_zlabel('')
# Draw custom axis lines through the center with arrows
axis_color = 'lightgray'
axis_alpha = 0.6
arrow_length = max_range * 0.95
# X-axis with arrow
ax.quiver(x_center, y_center, z_center, arrow_length, 0, 0,
color=axis_color, alpha=axis_alpha, arrow_length_ratio=0.15,
linewidth=1.5, zorder=0)
ax.quiver(x_center, y_center, z_center, -arrow_length, 0, 0,
color=axis_color, alpha=axis_alpha*0.5, arrow_length_ratio=0,
linewidth=1.5, zorder=0)
# Y-axis with arrow
ax.quiver(x_center, y_center, z_center, 0, arrow_length, 0,
color=axis_color, alpha=axis_alpha, arrow_length_ratio=0.15,
linewidth=1.5, zorder=0)
ax.quiver(x_center, y_center, z_center, 0, -arrow_length, 0,
color=axis_color, alpha=axis_alpha*0.5, arrow_length_ratio=0,
linewidth=1.5, zorder=0)
# Z-axis with arrow
ax.quiver(x_center, y_center, z_center, 0, 0, arrow_length,
color=axis_color, alpha=axis_alpha, arrow_length_ratio=0.15,
linewidth=1.5, zorder=0)
ax.quiver(x_center, y_center, z_center, 0, 0, -arrow_length,
color=axis_color, alpha=axis_alpha*0.5, arrow_length_ratio=0,
linewidth=1.5, zorder=0)
# Add axis labels at the end of each axis
label_offset = max_range * 1.05
ax.text(x_center + label_offset, y_center, z_center, 'X', color='lightgray', fontsize=12, ha='center')
ax.text(x_center, y_center + label_offset, z_center, 'Y', color='lightgray', fontsize=12, ha='center')
ax.text(x_center, y_center, z_center + label_offset, 'Z', color='lightgray', fontsize=12, ha='center')
ax.set_title('Lorenz Attractor', color='lightgray', fontsize=14, pad=20)
# Adjust viewing angle for better visualization
ax.view_init(elev=20, azim=45)
def init():
"""Initialize animation"""
for line, point in zip(lines, points):
line.set_data([], [])
line.set_3d_properties([])
point.set_data([], [])
point.set_3d_properties([])
return lines + points
def animate(frame):
"""Animation function - gradually reveals the trajectories with fading"""
# Slower animation - show fewer points per frame
n_points = int((frame / 200) * len(t))
if n_points > 1:
# Calculate current time
current_time = t[min(n_points-1, len(t)-1)]
# Calculate visible window based on fading rules
# Start fading at t=10, complete fade by t=30
if current_time < 10:
# Before t=10: show everything from start
start_idx = 0
elif current_time <= 30:
# From t=10 to t=30: gradually fade from beginning
fade_progress = (current_time - 10) / 20 # 0 to 1
start_time = fade_progress * (current_time - 10)
start_idx = int(start_time / dt_per_point)
else:
# After t=30: maintain 20-second sliding window
start_time = current_time - 20
start_idx = int(start_time / dt_per_point)
# Ensure indices are within bounds
start_idx = max(0, start_idx)
end_idx = min(n_points, len(t))
# Update each trajectory
for i, (line, point, traj) in enumerate(zip(lines, points, all_trajectories)):
x_traj = traj[:, 0]
y_traj = traj[:, 1]
z_traj = traj[:, 2]
# Update the trajectory line with visible window
line.set_data(x_traj[start_idx:end_idx], y_traj[start_idx:end_idx])
line.set_3d_properties(z_traj[start_idx:end_idx])
# Update the current position point
if end_idx > 0:
point.set_data([x_traj[end_idx-1]], [y_traj[end_idx-1]])
point.set_3d_properties([z_traj[end_idx-1]])
# Rotate the view slightly for dynamic visualization
ax.view_init(elev=20, azim=45 + frame * 0.3)
return lines + points
# Create animation with more frames for slower motion
anim = FuncAnimation(fig, animate, init_func=init,
frames=200, interval=50, blit=False, repeat=True)
plt.close(fig)
# Convert to HTML5 video
video_html = anim.to_html5_video()
video_html = video_html.replace('controls', 'controls autoplay loop muted')
video_html = video_html.replace('<video ', '<video style="width: 100%; height: auto;" ')
HTML(video_html)