-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path08_pendulum.py
More file actions
188 lines (159 loc) · 6.1 KB
/
08_pendulum.py
File metadata and controls
188 lines (159 loc) · 6.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python3
"""Demo script for Phase 1.3: Joints & Constraints.
This demonstrates a simple pendulum created using a revolute joint.
The pendulum should swing back and forth realistically.
"""
# IMPORTANT: Set environment variable BEFORE any imports
import os
if "RAPIER_SERVICE_URL" not in os.environ:
os.environ["RAPIER_SERVICE_URL"] = "https://rapier.chukai.io"
import asyncio
import math
from chuk_mcp_physics.providers.rapier import RapierProvider
from chuk_mcp_physics.models import (
SimulationConfig,
RigidBodyDefinition,
JointDefinition,
BodyType,
ShapeType,
JointType,
)
async def main():
print("🎢 Phase 1.3 Demo: Simple Pendulum with Revolute Joint\n")
print("=" * 60)
# Use environment variable or default
service_url = os.environ.get("RAPIER_SERVICE_URL", "https://rapier.chukai.io")
print(f"Using Rapier service: {service_url}\n")
provider = RapierProvider()
# Create simulation
print("1️⃣ Creating simulation...")
sim = await provider.create_simulation(
SimulationConfig(
gravity=[0.0, -9.81, 0.0],
dimensions=3,
dt=0.016,
)
)
print(f" ✓ Simulation created: {sim.sim_id}\n")
# Add fixed anchor point at top
print("2️⃣ Adding anchor point...")
await provider.add_body(
sim.sim_id,
RigidBodyDefinition(
id="anchor",
kind=BodyType.STATIC,
shape=ShapeType.SPHERE,
size=[0.05], # 5cm radius
position=[0.0, 5.0, 0.0], # 5m high
friction=0.0,
restitution=0.0,
),
)
print(" ✓ Anchor added at (0, 5, 0)\n")
# Add pendulum bob - start at angle
print("3️⃣ Adding pendulum bob...")
pendulum_length = 2.0 # 2 meter pendulum
initial_angle = math.radians(45) # Start at 45 degrees
bob_x = pendulum_length * math.sin(initial_angle)
bob_y = 5.0 - pendulum_length * math.cos(initial_angle)
await provider.add_body(
sim.sim_id,
RigidBodyDefinition(
id="bob",
kind=BodyType.DYNAMIC,
shape=ShapeType.SPHERE,
size=[0.2], # 20cm radius
mass=1.0, # 1kg
position=[bob_x, bob_y, 0.0],
velocity=[0.0, 0.0, 0.0],
friction=0.1,
restitution=0.1,
),
)
print(f" ✓ Bob added at ({bob_x:.2f}, {bob_y:.2f}, 0)\n")
# Connect with revolute joint (hinge)
print("4️⃣ Creating revolute joint...")
joint_id = await provider.add_joint(
sim.sim_id,
JointDefinition(
id="pendulum_hinge",
joint_type=JointType.REVOLUTE,
body_a="anchor",
body_b="bob",
anchor_a=[0.0, 0.0, 0.0], # Center of anchor
anchor_b=[0.0, 0.2, 0.0], # Top of bob
axis=[0.0, 0.0, 1.0], # Rotate around Z-axis
),
)
print(f" ✓ Joint created: {joint_id}\n")
# Record trajectory
print("5️⃣ Recording pendulum swing (10 seconds, 625 frames)...")
steps = 625 # 10 seconds at 0.016s per step
trajectory = await provider.record_trajectory(
sim_id=sim.sim_id,
body_id="bob",
steps=steps,
)
print(f" ✓ Recorded {len(trajectory.frames)} frames\n")
# Analyze trajectory
print("6️⃣ Analyzing pendulum motion...\n")
print("=" * 60)
# Find min/max heights (should show oscillation)
heights = [frame.position[1] for frame in trajectory.frames]
min_height = min(heights)
max_height = max(heights)
# Find x positions (should oscillate left/right)
x_positions = [frame.position[0] for frame in trajectory.frames]
min_x = min(x_positions)
max_x = max(x_positions)
print(f"Height range: {min_height:.3f}m to {max_height:.3f}m")
print(f"X range: {min_x:.3f}m to {max_x:.3f}m\n")
# Calculate period (time between peaks)
# Theoretical period: T = 2π√(L/g) = 2π√(2/9.81) ≈ 2.84 seconds
theoretical_period = 2 * math.pi * math.sqrt(pendulum_length / 9.81)
print(f"Theoretical period: {theoretical_period:.3f}s\n")
# Find peaks in x position
peaks = []
for i in range(1, len(trajectory.frames) - 1):
prev_x = trajectory.frames[i - 1].position[0]
curr_x = trajectory.frames[i].position[0]
next_x = trajectory.frames[i + 1].position[0]
# Local maximum
if curr_x > prev_x and curr_x > next_x and abs(curr_x) > 0.1:
peaks.append((trajectory.frames[i].time, curr_x))
if len(peaks) >= 2:
print("Detected swing peaks:")
for i, (time, x_pos) in enumerate(peaks[:5]):
print(f" Peak #{i + 1}: t={time:.3f}s, x={x_pos:.3f}m")
# Calculate observed period
if len(peaks) >= 2:
time_diffs = [peaks[i + 1][0] - peaks[i][0] for i in range(len(peaks) - 1)]
avg_period = sum(time_diffs) / len(time_diffs)
print(f"\nObserved period: {avg_period:.3f}s")
print(f"Difference from theory: {abs(avg_period - theoretical_period):.3f}s")
print(
f"Accuracy: {(1 - abs(avg_period - theoretical_period) / theoretical_period) * 100:.1f}%"
)
else:
print("⚠️ Not enough peaks detected for period calculation")
# Sample trajectory points
print("\n" + "=" * 60)
print("Sample trajectory points:\n")
sample_indices = [0, 156, 312, 468, 624] # Every 2.5 seconds
for idx in sample_indices:
if idx < len(trajectory.frames):
frame = trajectory.frames[idx]
print(
f"t={frame.time:.2f}s: pos=({frame.position[0]:6.3f}, {frame.position[1]:6.3f}, {frame.position[2]:6.3f})"
)
# Cleanup
print("\n" + "=" * 60)
print("7️⃣ Cleaning up...")
await provider.destroy_simulation(sim.sim_id)
print(" ✓ Simulation destroyed\n")
print("=" * 60)
print("✨ Phase 1.3 Demo Complete!\n")
print("The pendulum swings realistically using the revolute joint.")
print("Joints successfully connect and constrain rigid bodies!")
if __name__ == "__main__":
asyncio.run(main())