Files
guadaloop_lev_control/RL Testing/ENV_INTEGRATION.md
2025-12-10 15:50:20 -06:00

177 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LevPodEnv Integration Summary
## Overview
`LevPodEnv` now fully interfaces with PyBullet simulation and uses the `maglev_predictor` to apply electromagnetic forces based on real-time gap heights and coil currents.
## Architecture
### System Configuration (From pod.xml and visualize_urdf.py)
- **Inverted Maglev System**: Pod hangs BELOW track (like a monorail)
- **Track**: Bottom surface at Z=0, 2m × 0.4m × 0.02m
- **Pod Mass**: 5.8 kg (from pod.xml inertial)
- **Yoke Positions** (local coordinates):
- Front Right: (+0.1259m, +0.0508m, +0.08585m)
- Front Left: (+0.1259m, -0.0508m, +0.08585m)
- Back Right: (-0.1259m, +0.0508m, +0.08585m)
- Back Left: (-0.1259m, -0.0508m, +0.08585m)
- **Y-axis distance between left/right**: 0.1016m
### Coil Configuration
- **Two Coils**:
- `coilL`: Left side (+Y), controls all +Y yokes
- `coilR`: Right side (-Y), controls all -Y yokes
- **Parameters** (preserved from original):
- Resistance: 1.1Ω
- Inductance: 0.0025H (2.5mH)
- Source Voltage: 12V
- Max Current: 10.2A
## Action Space
- **Type**: Box(2)
- **Range**: [-1, 1] for each coil
- **Mapping**:
- `action[0]`: PWM duty cycle for left coil (+Y side)
- `action[1]`: PWM duty cycle for right coil (-Y side)
## Observation Space
- **Type**: Box(5)
- **Components**:
1. `avg_gap_front` (m): Average gap height of front left & right yokes
2. `avg_gap_back` (m): Average gap height of back left & right yokes
3. `roll` (rad): Roll angle about X-axis (calculated from yoke Z positions)
4. `roll_rate` (rad/s): Angular velocity about X-axis
5. `z_velocity` (m/s): Vertical velocity
## Physics Pipeline (per timestep)
### 1. Coil Current Update
```python
currL = self.coilL.update(pwm_L, dt) # First-order RL circuit model
currR = self.coilR.update(pwm_R, dt)
```
### 2. Gap Height Calculation
For each of 4 yokes:
- Transform local position to world coordinates using rotation matrix
- Add 5mm (half-height of 10mm yoke box) to get top surface
- Gap height = -yoke_top_z (track at Z=0, yoke below)
- Separate into front and back averages
### 3. Roll Angle Calculation
```python
roll = arctan2((right_z_avg - left_z_avg) / y_distance)
```
- Uses Z-position difference between left (+Y) and right (-Y) yokes
- Y-distance = 0.1016m (distance between yoke centerlines)
### 4. Force/Torque Prediction
```python
# Convert to Ansys convention (negative currents)
currL_ansys = -abs(currL)
currR_ansys = -abs(currR)
# Predict for front and back independently
force_front, torque_front = predictor.predict(currL_ansys, currR_ansys, roll_deg, gap_front_mm)
force_back, torque_back = predictor.predict(currL_ansys, currR_ansys, roll_deg, gap_back_mm)
```
### 5. Force Application
- **Front Force**: Applied at [+0.1259, 0, 0.08585] in local frame
- **Back Force**: Applied at [-0.1259, 0, 0.08585] in local frame
- **Roll Torque**: Average of front/back torques, applied about X-axis
- Converted from mN·m to N·m: `torque_Nm = avg_torque / 1000`
### 6. Simulation Step
```python
p.stepSimulation() # 240 Hz (dt = 1/240s)
```
## Reward Function
```python
reward = 1.0
reward -= gap_error * 100 # Target: 10mm gap
reward -= roll_error * 50 # Keep level
reward -= z_vel_penalty * 10 # Minimize oscillation
reward -= power * 0.01 # Efficiency
```
## Termination Conditions
- Gap outside [2mm, 30mm] range
- Roll angle exceeds ±10°
## Info Dictionary
Each step returns:
```python
{
'currL': float, # Left coil current (A)
'currR': float, # Right coil current (A)
'gap_front': float, # Front average gap (m)
'gap_back': float, # Back average gap (m)
'roll': float, # Roll angle (rad)
'force_front': float, # Front force prediction (N)
'force_back': float, # Back force prediction (N)
'torque': float # Average torque (mN·m)
}
```
## Key Design Decisions
### Why Two Coils Instead of Four?
- Physical system has one coil per side (left/right)
- Each coil's magnetic field affects both front and back yokes on that side
- Simplifies control: differential current creates roll torque
### Why Separate Front/Back Predictions?
- Gap heights can differ due to pitch angle
- More accurate force modeling
- Allows pitch control if needed in future
### Roll Angle from Yoke Positions
As requested: `roll = arctan((right_z - left_z) / y_distance)`
- Uses actual yoke Z positions in world frame
- More accurate than quaternion-based roll (accounts for deformation)
- Matches physical sensor measurements
### Current Sign Convention
- Coils produce positive current (0 to +10.2A)
- Ansys model expects negative currents (-15A to 0A)
- Conversion: `currL_ansys = -abs(currL)`
## Usage Example
```python
from lev_pod_env import LevPodEnv
# Create environment
env = LevPodEnv(use_gui=True) # Set False for training
# Reset
obs, info = env.reset()
# obs = [gap_front, gap_back, roll, roll_rate, z_vel]
# Step
action = [0.5, 0.5] # 50% PWM on both coils
obs, reward, terminated, truncated, info = env.step(action)
# Check results
print(f"Gaps: {info['gap_front']*1000:.2f}mm, {info['gap_back']*1000:.2f}mm")
print(f"Forces: {info['force_front']:.2f}N, {info['force_back']:.2f}N")
print(f"Currents: {info['currL']:.2f}A, {info['currR']:.2f}A")
env.close()
```
## Testing
Run `test_env.py` to verify integration:
```bash
cd "/Users/adipu/Documents/lev_control_4pt_small/RL Testing"
/opt/miniconda3/envs/RLenv/bin/python test_env.py
```
## Next Steps for RL Training
1. Test environment with random actions (test_env.py)
2. Verify force magnitudes are reasonable (should see ~50-100N upward)
3. Check that roll control works (differential currents produce torque)
4. Train RL agent (PPO, SAC, or TD3 recommended)
5. Tune reward function weights based on training results