code is folded so the argument and saved outputs come first. expand any cell to inspect the implementation. saved outputs are historical evidence from the source notebook, not a new run or independent verification.
Ant policy learning: objective → policy → behavior¶
This notebook is a deliberately incomplete learning scaffold. Your job is to write the TODO cells in VS Code and use the outputs to connect observation → policy → action → MuJoCo step → reward → policy update → changed behavior.
Sources: Farama custom quadruped, Ant-v5, and Stable-Baselines3 guidance.
Goal¶
Use Ant-v5 as a temporary quadruped to study how an objective can produce intended motion or an exploitable behavior. This is not C-1N code or C-1N evidence. Before defining the misspecified treatment, preserve this initial prediction:
speed up to a high peak velocity without moving much
Setup¶
Run the next two cells first. The smoke cell resets Ant, samples one legal random action, and steps once. It does not train a policy.
inspect code · cell 01
from pathlib import Path
import gymnasium as gym
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from stable_baselines3 import PPO
ENV_ID = "Ant-v5"
SMOKE_TIMESTEPS = 10_000
MAX_TRAINING_TIMESTEPS = 1_000_000
EVAL_SEEDS = [0, 1, 2, 3, 4]
POLICY_NET_ARCH = [64, 64]
EXPERIMENT_DIR = Path.cwd()
ARTIFACTS_DIR = EXPERIMENT_DIR / "artifacts"
CHECKPOINTS_DIR = ARTIFACTS_DIR / "checkpoints"
LOGS_DIR = EXPERIMENT_DIR / "logs"
VIDEOS_DIR = EXPERIMENT_DIR / "videos"
ROLLOUTS_DIR = EXPERIMENT_DIR / "rollouts"
EXPECTED_METRICS = [
"total_return",
"net_displacement",
"peak_absolute_velocity",
"survival_time",
"control_cost",
"contact_cost",
"termination_cause",
]
assert SMOKE_TIMESTEPS < MAX_TRAINING_TIMESTEPS
assert EVAL_SEEDS == [0, 1, 2, 3, 4]
assert POLICY_NET_ARCH == [64, 64]
assert set(EXPECTED_METRICS) == {
"total_return", "net_displacement", "peak_absolute_velocity",
"survival_time", "control_cost", "contact_cost", "termination_cause",
}
inspect code · cell 02
# Complete smoke path: reset, sample a legal action, and take one MuJoCo step.
smoke_env = gym.make(ENV_ID)
smoke_observation, smoke_info = smoke_env.reset(seed=0)
smoke_action = smoke_env.action_space.sample()
next_observation, smoke_reward, smoke_terminated, smoke_truncated, next_info = smoke_env.step(smoke_action)
assert smoke_env.observation_space.contains(smoke_observation)
assert smoke_env.action_space.contains(smoke_action)
assert smoke_env.observation_space.contains(next_observation)
assert isinstance(smoke_reward, float)
print({
"observation_shape": smoke_observation.shape,
"action_shape": smoke_action.shape,
"reward": smoke_reward,
"terminated": smoke_terminated,
"truncated": smoke_truncated,
})
smoke_env.close()
Steps¶
Each TODO is intentionally yours. Implement one, run it, inspect its output, and write down what changed in the loop before moving on.
1. Inspect the Ant interface¶
TODO: inspect the MuJoCo model, simulator timestep, observation structure, action bounds, and reset state. Which quantities are available to the policy, and which action dimensions reach the actuators?
inspect code · cell 03
# TODO(user): create an Ant environment and inspect model/timestep/observation/action/reset details.
def inspect_ant_interface():
raise NotImplementedError("TODO(user): inspect Ant-v5 before training.")
2. Record a random-action rollout¶
TODO: record state, action, decomposed reward terms, termination/truncation, position, and velocity. Keep unavailable measurements explicit instead of filling them with guesses.
inspect code · cell 04
# TODO(user): return a pandas DataFrame with one row per random-action transition.
def record_random_rollout(env, seed, steps):
raise NotImplementedError("TODO(user): record rollout telemetry.")
# TODO(user): choose the columns after inspecting the environment info dictionary.
rollout_columns = ["step", "state", "action", "reward", "terminated", "truncated"]
random_rollout = pd.DataFrame(columns=rollout_columns)
3. Trace one transition¶
TODO: select a single row and show the causal path state → action → dynamics → next state → reward. What information is produced by the environment, and what information is chosen by a policy?
inspect code · cell 05
# TODO(user): return a readable single-transition record from your rollout.
def trace_transition(rollout, step_index):
raise NotImplementedError("TODO(user): trace one recorded transition.")
4. Define treatment slots¶
Keep one control and one deliberately legible treatment. The misspecified objective is intentionally blank: decide whether a peak, absolute, or squared velocity quantity tests your prediction, then state why.
inspect code · cell 06
# TODO(user): define only the configuration fields you can explain.
treatments = {
"control": {},
"misspecified": {},
"corrected": {},
}
assert set(treatments) == {"control", "misspecified", "corrected"}
# TODO(user): choose and implement the misspecified reward; do not delegate the choice.
def misspecified_reward(transition):
raise NotImplementedError("TODO(user): choose peak, absolute, squared, or another stated velocity objective.")
5. Construct the small policy¶
TODO: construct PPO with MlpPolicy and two hidden layers of 64 units. Read the Stable-Baselines3 constructor while you decide the remaining hyperparameters; do not copy a configuration you cannot explain.
inspect code · cell 07
# TODO(user): construct PPO("MlpPolicy", ...) with net_arch=POLICY_NET_ARCH.
def build_policy(training_env, seed):
raise NotImplementedError("TODO(user): construct the small PPO policy.")
6. Train headlessly, then resume¶
TODO: implement a 10_000-step headless smoke run before a resumable run up to 1_000_000 steps. Put checkpoints and TensorBoard logs beneath artifacts/; do not render during training.
inspect code · cell 08
# TODO(user): add your checkpoint callback and resumable training path.
def train_headlessly(model, total_timesteps, checkpoint_dir, log_dir):
raise NotImplementedError("TODO(user): run a 10k smoke training pass, then resume deliberately.")
assert SMOKE_TIMESTEPS == 10_000
assert MAX_TRAINING_TIMESTEPS == 1_000_000
7. Evaluate on fixed seeds¶
TODO: evaluate deterministic policies on seeds [0, 1, 2, 3, 4]. Keep the training objective separate from evaluation metrics. One training seed per treatment is enough for this learning fixture, not for a benchmark claim.
inspect code · cell 09
# TODO(user): return one metric row per treatment and evaluation seed.
def evaluate_deterministically(model, treatment_name, seeds=EVAL_SEEDS):
raise NotImplementedError("TODO(user): evaluate deterministic rollouts on fixed seeds.")
evaluation_table = pd.DataFrame(columns=["treatment", "seed", *EXPECTED_METRICS])
assert evaluation_table.columns.tolist() == ["treatment", "seed", *EXPECTED_METRICS]
8. Compare treatments and render only evaluation¶
TODO: fill the plot with your evaluation table, then render a selected evaluation rollout as inline RGB frames. Do not use rendered training frames as evidence.
inspect code · cell 10
# TODO(user): replace the empty shell with a metric comparison after evaluation.
fig, axis = plt.subplots(figsize=(9, 4))
axis.set(
title="TODO: treatment comparison after fixed-seed evaluation",
xlabel="treatment",
ylabel="chosen evaluation metric",
)
axis.grid(alpha=0.25)
fig.tight_layout()
# TODO(user): create an env with render_mode="rgb_array" and display evaluation frames inline.
def render_evaluation_frames(model, seed):
raise NotImplementedError("TODO(user): render evaluation only, never training.")
Checks¶
Before interpreting behavior, check that each treatment has the same fixed evaluation seeds, all expected metrics, retained checkpoints/rollout provenance, and a stated termination cause. A visually attractive rollout is one sample, not a conclusion.
Next steps: C-1N transfer worksheet (prompts only)¶
Do not write C-1N code here. When the Ant loop is understandable, answer these prompts before creating an adapter:
- What makes a C-1N reset deterministic, and what scenario state must be recorded?
- Which observation-vector quantities are available to a policy, with units and ordering?
- Which 12 actuator actions are exposed, and what are their bounds and meanings?
- What control timestep and frame skip connect one policy action to MuJoCo dynamics?
- Which termination conditions are physical failures versus time limits?
- What decomposed objective terms express intended motion and discourage exploitable behavior?
- How will checkpoints preserve policy, objective version, simulator version, seed, and rollout state?
- Which fixed scenarios and seeds will evaluate a policy independently of its training objective?