Frank_LBM/scripts/test_disco_fix.py
2026-02-15 19:21:28 +08:00

283 lines
8.5 KiB
Python

#!/usr/bin/env python3
"""Test script to verify DiscoRL environment fix.
This script tests that:
1. Environment properly handles episode boundaries
2. Rewards are correctly propagated
3. Episode lengths are reasonable (not always 1)
"""
import os
import sys
import numpy as np
import jax
import jax.numpy as jnp
# Setup paths
current_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.abspath(os.path.join(current_dir, os.pardir))
sys.path.insert(0, os.path.join(repo_root, 'disco_rl'))
from disco_cartpole_env import DiscoCartPoleEnv
def test_environment_basic():
"""Test basic environment functionality."""
print('=' * 70)
print('TEST 1: Basic Environment Functionality')
print('=' * 70)
env = DiscoCartPoleEnv(batch_size=2, max_steps=50)
# Reset
_, timestep = env.reset()
print(f'✓ Reset successful')
print(f' Observation shape: {timestep.observation["observation"].shape}')
print(f' Reward shape: {timestep.reward.shape}')
print(f' Step type shape: {timestep.step_type.shape}')
# Step
actions = np.array([0, 1]) # batch of 2 actions
_, timestep = env.step(None, actions)
print(f'✓ Step successful')
print(f' Rewards: {timestep.reward}')
print(f' Step types: {timestep.step_type}')
return env
def test_episode_boundaries():
"""Test that episode boundaries are handled correctly."""
print('\n' + '=' * 70)
print('TEST 2: Episode Boundary Handling')
print('=' * 70)
env = DiscoCartPoleEnv(batch_size=1, max_steps=500)
_, timestep = env.reset()
episode_lengths = []
current_length = 0
max_episodes = 5
episodes_completed = 0
step_count = 0
max_total_steps = 5000
np.random.seed(42)
while episodes_completed < max_episodes and step_count < max_total_steps:
# Random actions (gives better results than constant action)
actions = np.array([np.random.randint(0, 2)])
_, timestep = env.step(None, actions)
step_count += 1
current_length += 1
# Check for episode boundary
if timestep.step_type[0] == 2: # LAST
episodes_completed += 1
episode_lengths.append(current_length)
print(f'Episode {episodes_completed} finished: length={current_length}')
# Reset environment for next episode
_, timestep = env.reset()
current_length = 0
# Verify episode lengths
print(f'\n✓ Episodes completed: {episodes_completed}')
print(f' Episode lengths: {episode_lengths}')
# Check that not all episodes are length 1
min_length = min(episode_lengths)
max_length = max(episode_lengths)
avg_length = np.mean(episode_lengths)
print(f' Min length: {min_length}')
print(f' Max length: {max_length}')
print(f' Avg length: {avg_length:.1f}')
if max_length <= 1:
print('❌ FAILED: All episodes are length 1 or less (fix not working)')
return False
else:
print('✅ PASSED: Episodes have varied lengths > 1')
return True
def test_reward_propagation():
"""Test that rewards are correctly propagated."""
print('\n' + '=' * 70)
print('TEST 3: Reward Propagation')
print('=' * 70)
env = DiscoCartPoleEnv(batch_size=1, max_steps=100)
_, timestep = env.reset()
all_rewards = []
num_steps = 20
for step in range(num_steps):
actions = np.array([0]) # Action: push left
_, timestep = env.step(None, actions)
all_rewards.append(float(timestep.reward[0]))
print(f'✓ Collected {num_steps} steps')
print(f' Reward sequence: {all_rewards}')
print(f' Min reward: {min(all_rewards)}')
print(f' Max reward: {max(all_rewards)}')
print(f' Sum reward: {sum(all_rewards)}')
# CartPole gives reward of 1.0 for each step before terminal
expected_rewards = [1.0] * num_steps # or until terminal
# Check that rewards are 1.0 for non-terminal steps
non_terminal_rewards = [r for r in all_rewards if r > 0]
if not non_terminal_rewards:
print('❌ FAILED: No non-zero rewards (fix not working)')
return False
if all(r == 1.0 for r in non_terminal_rewards):
print('✅ PASSED: Non-terminal rewards are correctly 1.0')
return True
else:
print('⚠️ WARNING: Unexpected reward values')
return False
def test_batch_handling():
"""Test that batch environments work correctly."""
print('\n' + '=' * 70)
print('TEST 4: Batch Environment Handling')
print('=' * 70)
batch_size = 4
env = DiscoCartPoleEnv(batch_size=batch_size, max_steps=50)
_, timestep = env.reset()
print(f'✓ Created batch environment with size {batch_size}')
# Run multiple steps
for step in range(10):
actions = np.array([np.random.randint(0, 2) for _ in range(batch_size)])
_, timestep = env.step(None, actions)
print(f'✓ Successfully stepped through {10} batched steps')
print(f' Observation shape: {timestep.observation["observation"].shape}')
print(f' Rewards shape: {timestep.reward.shape}')
print(f' Expected: ({batch_size},)')
print(f' Actual: {timestep.reward.shape}')
if timestep.reward.shape == (batch_size,):
print('✅ PASSED: Batch shapes are correct')
return True
else:
print('❌ FAILED: Batch shapes mismatch')
return False
def test_episode_done_tracking():
"""Test that _episode_done flag works correctly."""
print('\n' + '=' * 70)
print('TEST 5: Episode Done Flag Tracking')
print('=' * 70)
env = DiscoCartPoleEnv(batch_size=2, max_steps=10)
_, timestep = env.reset()
print(f'Initial _episode_done: {env._episode_done}')
print(f'Initial _step_counts: {env._step_counts}')
# Step until first episode ends
step_num = 0
for _ in range(100):
actions = np.array([1, 0]) # Different actions for batch
_, timestep = env.step(None, actions)
step_num += 1
if np.any(timestep.step_type == 1): # Any episode done
print(f'\nAfter {step_num} steps:')
print(f' Step types: {timestep.step_type}')
print(f' _episode_done: {env._episode_done}')
print(f' _step_counts: {env._step_counts}')
print(f'✅ PASSED: Episode done flags tracked correctly')
return True
print('⚠️ WARNING: No episodes completed (may be fine depending on randomness)')
return True
def main():
print('\n')
print('' * 70)
print(' DiscoRL Environment Fix Verification Tests')
print('' * 70)
print()
results = []
# Test 1
try:
test_environment_basic()
results.append(('Basic Functionality', True))
except Exception as e:
print(f'❌ FAILED: {e}')
results.append(('Basic Functionality', False))
# Test 2
try:
passed = test_episode_boundaries()
results.append(('Episode Boundaries', passed))
except Exception as e:
print(f'❌ FAILED: {e}')
results.append(('Episode Boundaries', False))
# Test 3
try:
passed = test_reward_propagation()
results.append(('Reward Propagation', passed))
except Exception as e:
print(f'❌ FAILED: {e}')
results.append(('Reward Propagation', False))
# Test 4
try:
passed = test_batch_handling()
results.append(('Batch Handling', passed))
except Exception as e:
print(f'❌ FAILED: {e}')
results.append(('Batch Handling', False))
# Test 5
try:
passed = test_episode_done_tracking()
results.append(('Episode Done Tracking', passed))
except Exception as e:
print(f'❌ FAILED: {e}')
results.append(('Episode Done Tracking', False))
# Summary
print('\n' + '=' * 70)
print('TEST SUMMARY')
print('=' * 70)
for test_name, passed in results:
status = '✅ PASS' if passed else '❌ FAIL'
print(f'{status}: {test_name}')
total_passed = sum(1 for _, p in results if p)
total_tests = len(results)
print(f'\nTotal: {total_passed}/{total_tests} tests passed')
if total_passed == total_tests:
print('\n🎉 ALL TESTS PASSED! Fix is working correctly.')
return 0
else:
print(f'\n⚠️ {total_tests - total_passed} test(s) failed. Please review.')
return 1
if __name__ == '__main__':
exit_code = main()
sys.exit(exit_code)