Skip to content

Commit 20eb421

Browse files
Add Homework 1: Q-iteration for MountainCar
Includes: - Theory PDF with written problems - Problem 1: Tabular Q-iteration implementation - Precomputed transition tables (200x200 discretization) - Submission instructions and leaderboard link - Policy template for students to implement
1 parent 1b916e8 commit 20eb421

11 files changed

Lines changed: 649 additions & 5 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,7 @@ tensorboard/
219219
resources
220220
server/leaderboard.db
221221
*.db
222+
223+
# Homework solutions (keep private)
224+
*solutions*
225+
CLAUDE.md

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ Reinforcement Learning course project using [PufferLib](https://github.com/Puffe
1313
curl -LsSf https://astral.sh/uv/install.sh | sh
1414
```
1515

16-
**Windows:**
17-
```powershell
18-
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
19-
```
20-
2116
**Homebrew (macOS):**
2217
```bash
2318
brew install uv
47.4 KB
Binary file not shown.

homeworks/homework_1/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Homework 1: Value-Based Reinforcement Learning
2+
3+
This homework covers tabular Q-learning methods.
4+
5+
## Structure
6+
7+
- `Homework_1_theory.pdf` - Theory questions (submit on Gradescope)
8+
- `problem_1/` - Tabular Q-iteration for MountainCar
9+
10+
## Submission Instructions
11+
12+
### Theory (Gradescope)
13+
Submit your answers to the theory questions as a PDF on Brightspace.
14+
15+
### Programming Problems (Leaderboard)
16+
17+
For each programming problem, submit to the course leaderboard:
18+
19+
**Problem 1: MountainCar Q-Iteration**
20+
- Submit: `policy.py` and `checkpoint.pt`
21+
- Your policy must achieve mean reward > -150
22+
23+
### How to Submit to Leaderboard
24+
25+
1. Go to the course leaderboard: https://eval-server-production-c3fe.up.railway.app/
26+
2. Select MountainCar
27+
3. Upload your `policy.py` and `checkpoint.pt` files
28+
4. Wait for evaluation results
29+
30+
### File Requirements
31+
32+
Your `policy.py` must contain:
33+
- A `Policy` class with a `forward(obs)` method that returns an action
34+
- A `load_policy(checkpoint_path)` function that returns a Policy instance
35+
36+
Your `checkpoint.pt` must be loadable by your `load_policy` function.
37+
38+
## Getting Started
39+
40+
First, follow the setup instructions in the main [README](../../README.md) to install dependencies using `uv sync`.
41+
42+
Then:
43+
```bash
44+
# Activate the virtual environment
45+
source .venv/bin/activate
46+
47+
# Problem 1: Run Q-iteration
48+
cd homeworks/homework_1/problem_1
49+
python problem_1.py
50+
51+
# Test your policy locally
52+
python policy.py
53+
```
54+
55+
## Grading
56+
57+
- Theory questions: See Brightspace rubric
58+
- Programming problems: Pass/fail based on leaderboard performance thresholds
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Problem 1: Tabular Q-Iteration for MountainCar
2+
3+
In this problem, you will implement Q-iteration (dynamic programming) for the MountainCar environment. The continuous observation space has been discretized into a 200x200 grid.
4+
5+
## Environment
6+
7+
**MountainCar** has a car stuck in a valley that must build momentum to reach the goal on the right.
8+
9+
**Observation space (2 dimensions):**
10+
- Position: range [-1.2, 0.6], goal at position >= 0.5
11+
- Velocity: range [-0.07, 0.07]
12+
13+
**Actions:**
14+
- 0: Push left
15+
- 1: No push
16+
- 2: Push right
17+
18+
**Reward:** -1 for each timestep (encourages reaching the goal quickly)
19+
20+
## Transition Tables
21+
22+
We provide precomputed transition tables that describe the discretized environment dynamics.
23+
24+
> **WARNING:** Do NOT modify or regenerate the transition tables (`.npy` files). The grading server uses the same tables - changing them will cause your submission to fail.
25+
26+
### State Space Discretization
27+
28+
The state space is discretized into a 200x200 grid:
29+
30+
- **s0 (first index):** Position index, ranging from 0 to 199
31+
- Index 0 = position -1.2 (leftmost)
32+
- Index 199 = position 0.6 (rightmost)
33+
- Goal region (position >= 0.5) is roughly indices 189-199
34+
35+
- **s1 (second index):** Velocity index, ranging from 0 to 199
36+
- Index 0 = velocity -0.07 (moving left fastest)
37+
- Index 199 = velocity 0.07 (moving right fastest)
38+
- Index ~100 = velocity ~0 (stationary)
39+
40+
### Table Formats
41+
42+
**P (transition_next_states.npy):** shape `(200, 200, 3, 2)`
43+
44+
```python
45+
P[s0, s1, a] = [s0', s1'] # numpy array of 2 integers
46+
```
47+
48+
Given current state indices (s0, s1) and action a, returns the next state indices.
49+
50+
Example:
51+
```python
52+
next_state = P[100, 100, 2] # State (100,100), action 2 (push right)
53+
s0_next, s1_next = next_state[0], next_state[1]
54+
# Now you can look up Q[s0_next, s1_next, :] to get Q-values at next state
55+
```
56+
57+
**R (transition_rewards.npy):** shape `(200, 200, 3)`
58+
59+
```python
60+
R[s0, s1, a] = reward # single float, always -1.0
61+
```
62+
63+
The immediate reward for taking action a in state (s0, s1).
64+
65+
**D (transition_dones.npy):** shape `(200, 200, 3)`
66+
67+
```python
68+
D[s0, s1, a] = done # boolean: True or False
69+
```
70+
71+
Whether the episode terminates after taking action a. True only when the car reaches the goal.
72+
73+
## Your Task
74+
75+
### 1. Implement `q_iteration()` in `problem_1.py`
76+
77+
Implement Q-iteration using the Bellman optimality equation:
78+
79+
```
80+
Q(s, a) = R(s, a) + gamma * (1 - D(s, a)) * max_a' Q(s', a')
81+
```
82+
83+
where s' = P(s, a) is the next state.
84+
85+
**Algorithm:**
86+
1. Initialize Q-table to zeros: shape (200, 200, 3)
87+
2. Repeat until convergence or max_iterations:
88+
- For each state-action pair (s0, s1, a):
89+
- Look up next state: `s0', s1' = P[s0, s1, a]`
90+
- Look up reward: `r = R[s0, s1, a]`
91+
- Look up done: `d = D[s0, s1, a]`
92+
- Update: `Q_new[s0, s1, a] = r + gamma * (1 - d) * max_a' Q[s0', s1', a']`
93+
- If max|Q_new - Q| < theta: converged, stop
94+
- Q = Q_new
95+
3. Return the converged Q-table
96+
97+
### 2. Implement `forward()` in `policy.py`
98+
99+
Implement the action selection method:
100+
1. Discretize the observation using `self.discretize_state(obs)`
101+
2. Look up the Q-values for that state in `self.q_table`
102+
3. Return the action with the highest Q-value
103+
104+
## Running Your Solution
105+
106+
```bash
107+
python problem_1.py
108+
```
109+
110+
This will:
111+
1. Load the transition tables
112+
2. Run your Q-iteration implementation
113+
3. Save the Q-table to `checkpoint.pt`
114+
4. Evaluate your policy
115+
116+
## Submission
117+
118+
Submit two files to the leaderboard:
119+
- `checkpoint.pt` - Your Q-table saved with torch.save (shape: 200x200x3)
120+
- `policy.py` - With your implementation of the `forward()` method
121+
122+
## Grading
123+
124+
Your policy must achieve a **mean reward better than -150** to pass.
125+
126+
A well-implemented solution should:
127+
- Consistently reach the goal (position >= 0.5)
128+
- Complete episodes in fewer than 150 steps on average
129+
- Achieve 100% success rate
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
Script to build the transition table for MountainCar Q-iteration.
3+
Run this once to generate the transition files for students.
4+
5+
WARNING: Students should NOT run this script or modify the transition tables.
6+
The .npy files are provided as-is and will be used by the grading server.
7+
If you regenerate them, your solution may not work correctly during evaluation.
8+
"""
9+
10+
import numpy as np
11+
from tqdm import tqdm
12+
import gymnasium as gym
13+
14+
15+
N_BINS = 200
16+
N_ACTIONS = 3 # 0: push left, 1: no push, 2: push right
17+
18+
# MountainCar observation bounds
19+
STATE_BOUNDS = [
20+
(-1.2, 0.6), # Position
21+
(-0.07, 0.07), # Velocity
22+
]
23+
24+
25+
def discretize_state(observation):
26+
if observation.ndim > 1:
27+
observation = observation[0]
28+
indices = []
29+
for i, (low, high) in enumerate(STATE_BOUNDS):
30+
val = np.clip(observation[i], low, high)
31+
scaled = (val - low) / (high - low) * (N_BINS - 1)
32+
idx = int(np.clip(np.round(scaled), 0, N_BINS - 1))
33+
indices.append(idx)
34+
return tuple(indices)
35+
36+
37+
def undiscretize_state(indices):
38+
obs = []
39+
for i, (low, high) in enumerate(STATE_BOUNDS):
40+
val = low + (indices[i] / (N_BINS - 1)) * (high - low)
41+
obs.append(val)
42+
return np.array(obs)
43+
44+
45+
def build_transition_table():
46+
print("Building transition table for MountainCar...")
47+
env = gym.make("MountainCar-v0")
48+
49+
# Shape: (position_bins, velocity_bins, actions, next_state_dims)
50+
next_states = np.zeros((N_BINS, N_BINS, N_ACTIONS, 2), dtype=np.int32)
51+
rewards = np.zeros((N_BINS, N_BINS, N_ACTIONS), dtype=np.float32)
52+
dones = np.zeros((N_BINS, N_BINS, N_ACTIONS), dtype=bool)
53+
54+
for s0 in tqdm(range(N_BINS), desc="Building transitions"):
55+
for s1 in range(N_BINS):
56+
continuous_state = undiscretize_state((s0, s1))
57+
58+
for action in range(N_ACTIONS):
59+
# Reset and set state directly
60+
env.reset()
61+
env.unwrapped.state = continuous_state.copy()
62+
63+
obs, reward, terminated, truncated, _ = env.step(action)
64+
next_state_indices = discretize_state(obs)
65+
66+
next_states[s0, s1, action] = next_state_indices
67+
rewards[s0, s1, action] = reward
68+
dones[s0, s1, action] = terminated # Don't include truncated
69+
70+
env.close()
71+
72+
np.save("transition_next_states.npy", next_states)
73+
np.save("transition_rewards.npy", rewards)
74+
np.save("transition_dones.npy", dones)
75+
print("Saved: transition_next_states.npy, transition_rewards.npy, transition_dones.npy")
76+
77+
# Print some stats
78+
print(f"\nTransition table stats:")
79+
print(f" Shape P: {next_states.shape}")
80+
print(f" Shape R: {rewards.shape}")
81+
print(f" Shape D: {dones.shape}")
82+
print(f" Terminal states: {dones.sum()} / {dones.size} ({100*dones.sum()/dones.size:.1f}%)")
83+
print(f" Reward range: [{rewards.min()}, {rewards.max()}]")
84+
85+
86+
if __name__ == "__main__":
87+
build_transition_table()

0 commit comments

Comments
 (0)