Skip to content

Commit 626d3c0

Browse files
feat(matplotlib): implement phase-diagram (#3054)
## Implementation: `phase-diagram` - matplotlib Implements the **matplotlib** version of `phase-diagram`. **File:** `plots/phase-diagram/implementations/matplotlib.py` **Parent Issue:** #3004 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617611381)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 54d669f commit 626d3c0

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
""" pyplots.ai
2+
phase-diagram: Phase Diagram (State Space Plot)
3+
Library: matplotlib 3.10.8 | Python 3.13.11
4+
Quality: 93/100 | Created: 2025-12-31
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
10+
11+
# Damped harmonic oscillator: m*x'' + c*x' + k*x = 0
12+
# Using underdamped solution: x(t) = A*exp(-gamma*t)*cos(omega_d*t + phi)
13+
gamma = 0.15 # Damping coefficient
14+
omega0 = 1.0 # Natural frequency
15+
omega_d = np.sqrt(omega0**2 - gamma**2) # Damped frequency (underdamped case)
16+
17+
# Time array
18+
t = np.linspace(0, 50, 2000)
19+
20+
# Multiple trajectories from different initial conditions
21+
# Format: (A, phi) - amplitude and phase for analytical solution
22+
initial_params = [
23+
(3.0, 0.0), # Starting from rest, displaced right
24+
(3.0, 0.5), # Different phase
25+
(2.5, 2.5), # Another trajectory
26+
(2.0, 4.0), # Fourth trajectory
27+
]
28+
29+
# Compute trajectories using analytical solution
30+
# x(t) = A * exp(-gamma*t) * cos(omega_d*t + phi)
31+
# v(t) = dx/dt = A * exp(-gamma*t) * (-gamma*cos(omega_d*t + phi) - omega_d*sin(omega_d*t + phi))
32+
trajectories = []
33+
for A, phi in initial_params:
34+
exp_decay = A * np.exp(-gamma * t)
35+
x = exp_decay * np.cos(omega_d * t + phi)
36+
v = exp_decay * (-gamma * np.cos(omega_d * t + phi) - omega_d * np.sin(omega_d * t + phi))
37+
trajectories.append((x, v, A, phi))
38+
39+
# Colors for trajectories (Python Blue first, then accessible palette)
40+
colors = ["#306998", "#FFD43B", "#E55934", "#43AA8B"]
41+
42+
# Create figure
43+
fig, ax = plt.subplots(figsize=(16, 9))
44+
45+
# Plot each trajectory
46+
for i, (x, v, A, _phi) in enumerate(trajectories):
47+
# Plot trajectory line
48+
ax.plot(x, v, color=colors[i], linewidth=2.5, alpha=0.8, label=f"Trajectory {i + 1} (A={A:.1f})")
49+
50+
# Mark start point with larger marker
51+
ax.scatter(x[0], v[0], s=250, color=colors[i], edgecolor="white", linewidth=2, zorder=5, marker="o")
52+
53+
# Add arrows to show direction along trajectory
54+
n_points = len(x)
55+
n_arrows = 4
56+
arrow_indices = np.linspace(100, n_points - 200, n_arrows, dtype=int)
57+
for idx in arrow_indices:
58+
dx = x[idx + 10] - x[idx]
59+
dv = v[idx + 10] - v[idx]
60+
length = np.sqrt(dx**2 + dv**2)
61+
if length > 0.01:
62+
ax.annotate(
63+
"",
64+
xy=(x[idx + 10], v[idx + 10]),
65+
xytext=(x[idx], v[idx]),
66+
arrowprops={"arrowstyle": "->", "color": colors[i], "lw": 2.5, "mutation_scale": 20},
67+
)
68+
69+
# Mark the equilibrium point (stable fixed point at origin)
70+
ax.scatter(0, 0, s=400, color="black", marker="x", linewidth=4, zorder=10, label="Equilibrium (stable)")
71+
72+
# Add reference lines for axes
73+
ax.axhline(y=0, color="gray", linewidth=1.5, linestyle="--", alpha=0.5)
74+
ax.axvline(x=0, color="gray", linewidth=1.5, linestyle="--", alpha=0.5)
75+
76+
# Labels and styling
77+
ax.set_xlabel("Position x", fontsize=20)
78+
ax.set_ylabel("Velocity dx/dt", fontsize=20)
79+
ax.set_title("Damped Oscillator · phase-diagram · matplotlib · pyplots.ai", fontsize=24)
80+
ax.tick_params(axis="both", labelsize=16)
81+
ax.legend(fontsize=14, loc="upper right", framealpha=0.9)
82+
ax.grid(True, alpha=0.3, linestyle="--")
83+
84+
# Set equal aspect ratio for proper visualization
85+
ax.set_aspect("equal", adjustable="box")
86+
87+
# Adjust axis limits for better visualization
88+
ax.set_xlim(-4, 4)
89+
ax.set_ylim(-3.5, 3.5)
90+
91+
plt.tight_layout()
92+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: matplotlib
2+
specification_id: phase-diagram
3+
created: '2025-12-31T11:07:05Z'
4+
updated: '2025-12-31T11:16:07Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617611381
7+
issue: 3004
8+
python_version: 3.13.11
9+
library_version: 3.10.8
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/matplotlib/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/matplotlib/plot_thumb.png
12+
preview_html: null
13+
quality_score: 93
14+
review:
15+
strengths:
16+
- Excellent visualization of phase space dynamics with clear spiral convergence
17+
to equilibrium
18+
- Multiple trajectories from different initial conditions beautifully demonstrate
19+
the basin of attraction
20+
- Direction arrows effectively show time evolution as required by spec
21+
- Clean, publication-quality aesthetics with well-chosen color palette
22+
- Proper use of analytical solution ensures smooth, accurate trajectories
23+
weaknesses:
24+
- Axis labels lack units (e.g., "Position x (m)" or "(arbitrary units)" would be
25+
clearer)
26+
- Legend fontsize (14) could be increased to 16 for consistency with tick labels

0 commit comments

Comments
 (0)