2025-06-29 22:00:47 +02:00

129 lines
3.3 KiB
Python

import numpy as np
import plotly.graph_objects as go
"""
Interactive 3D Plotly animation of the scalar line integral
∮_C f(x, y) ds with f(x, y) = x · y
along the unit circle C, plotted on the surface z = f(x,y).
No files are written; the animation is displayed with interactive controls.
"""
# Parameters
t_frame = np.linspace(0.0, 2 * np.pi, 120)
# Scalar field definition
def scalar_field(x, y):
return x * y
# Surface mesh for scalar field
lin = np.linspace(-1.2, 1.2, 50)
X, Y = np.meshgrid(lin, lin)
Z = scalar_field(X, Y)
# Compute running integral along circle
# Midpoint rule
mid_ts = (t_frame[1:] + t_frame[:-1]) / 2
f_mid = scalar_field(np.cos(mid_ts), np.sin(mid_ts))
dt = t_frame[1:] - t_frame[:-1]
running = np.concatenate([[0], np.cumsum(f_mid * dt)])
# Prepare static surface trace
surface = go.Surface(
x=X, y=Y, z=Z, colorscale="RdBu", cmin=-1, cmax=1, opacity=0.6, showscale=False
)
# Full path on surface
curve_x = np.cos(t_frame)
curve_y = np.sin(t_frame)
curve_z = scalar_field(curve_x, curve_y)
path = go.Scatter3d(
x=curve_x, y=curve_y, z=curve_z, mode="lines", line=dict(color="black", width=2)
)
# Initial marker at t=0
t0 = t_frame[0]
marker = go.Scatter3d(
x=[np.cos(t0)],
y=[np.sin(t0)],
z=[scalar_field(np.cos(t0), np.sin(t0))],
mode="markers",
marker=dict(size=5, color="black"),
)
# Build frames for animation
frames = []
for i, t in enumerate(t_frame):
x0, y0 = np.cos(t), np.sin(t)
z0 = scalar_field(x0, y0)
frame = go.Frame(
data=[marker.update(x=[x0], y=[y0], z=[z0])],
layout=go.Layout(title_text=f"Running integral: {running[i]:.3f}"),
name=f"frame{i}",
)
frames.append(frame)
# Layout with sliders and buttons
graph = go.Figure(
data=[surface, path, marker],
layout=go.Layout(
title="Running integral: 0.000",
scene=dict(
xaxis=dict(range=[-1.3, 1.3]),
yaxis=dict(range=[-1.3, 1.3]),
zaxis=dict(range=[Z.min(), Z.max()]),
),
updatemenus=[
dict(
type="buttons",
showactive=False,
buttons=[
dict(
label="Play",
method="animate",
args=[
None,
dict(
frame=dict(duration=50, redraw=True),
fromcurrent=True,
transition=dict(duration=0),
),
],
)
],
)
],
),
frames=frames,
)
# Add slider
sliders = [
dict(
steps=[
dict(
method="animate",
args=[
[f.name],
dict(
mode="immediate",
frame=dict(duration=50, redraw=True),
transition=dict(duration=0),
),
],
label=str(i),
)
for i, f in enumerate(frames)
],
transition=dict(duration=0),
x=0,
y=0,
currentvalue=dict(prefix="Frame: "),
len=1.0,
)
]
graph.update_layout(sliders=sliders)
graph.show()