"""Train DiscoRL agent on CartPole using Disco103 discovered update rule. This script demonstrates: 1. Wrapping CartPole in DiscoRL's Environment interface 2. Loading Disco103 pre-trained weights 3. Running training loop with DiscoRL's agent and update rule 4. Collecting rollouts and computing losses 5. Saving trained agent for later evaluation Key insight: We use the Disco103 discovered update rule (meta-net) to guide the policy/value network training, much like how it was used in the original paper's meta-evaluation phase. Design notes: - DiscoRL expects discrete actions; CartPole is continuous but we discretize. - We do NOT do meta-training (i.e., we don't update the meta-net itself). Instead, we use the pre-trained meta-net to generate update targets for the policy/value network. - This is closer to "meta-evaluation" than "meta-training" in the paper's terminology. """ import os import sys # Force JAX to use CPU only (avoid GPU memory issues) os.environ['JAX_PLATFORMS'] = 'cpu' from typing import Any, Tuple import numpy as np import jax import jax.numpy as jnp from ml_collections import config_dict # Ensure DiscoRL is importable 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_rl import agent as disco_agent from disco_rl import types from disco_cartpole_env import DiscoCartPoleEnv from disco_weights import load_disco103_weights class DiscoTrainingState: """Manages training state for DiscoRL agent.""" def __init__( self, learner_state: Any, actor_state: Any, update_rule_params: Any, rng: jax.random.PRNGKey, ): self.learner_state = learner_state self.actor_state = actor_state self.update_rule_params = update_rule_params self.rng = rng def rollout_trajectory( agent: disco_agent.Agent, env: DiscoCartPoleEnv, training_state: DiscoTrainingState, trajectory_length: int, ) -> Tuple[types.ActorRollout, DiscoTrainingState]: """Collect one trajectory of experience using the agent. Args: agent: DiscoRL Agent instance env: environment training_state: current training state trajectory_length: number of steps to collect Returns: (rollout, updated_training_state) The rollout is a types.ActorRollout containing observations, actions, rewards, agent outputs, etc., collected over the trajectory. """ rng = training_state.rng learner_state = training_state.learner_state actor_state = training_state.actor_state update_rule_params = training_state.update_rule_params # Reset environment _, env_t = env.reset() # Collect trajectory observations = [] actions = [] rewards = [] discounts = [] # 1.0 for non-terminal, 0.0 for terminal agent_outs_list = [] logits_list = [] states = [] for step in range(trajectory_length): rng, subkey = jax.random.split(rng) # Agent step (policy + value network forward + action sampling) actor_timestep, actor_state = agent.actor_step( learner_state.params, subkey, env_t, actor_state, ) # Record states.append(actor_state) observations.append(env_t.observation['observation']) agent_outs_list.append(actor_timestep.agent_outs) logits_list.append(actor_timestep.logits) actions.append(actor_timestep.actions) # Environment step rng, subkey = jax.random.split(rng) _, env_t = env.step(None, actor_timestep.actions) rewards.append(env_t.reward) # discount: 1.0 if not terminal, 0.0 if terminal # LAST = 2 in dm_env StepType convention discounts.append(1.0 - (env_t.step_type == 2).astype(jnp.float32)) # Stack into batch dimensions # All shapes should be [T, B, ...] where T=trajectory_length, B=batch_size observations = jnp.stack(observations, axis=0) actions = jnp.stack(actions, axis=0) rewards = jnp.stack(rewards, axis=0) discounts = jnp.stack(discounts, axis=0) # For agent_outs, we need to stack each component agent_outs_stacked = jax.tree.map( lambda *xs: jnp.stack(xs, axis=0), *agent_outs_list, ) logits = jnp.stack(logits_list, axis=0) # Construct rollout (behavior = current agent, so behaviour_agent_out = agent_out) rollout = types.ActorRollout( observations=observations, actions=actions, rewards=rewards, discounts=discounts, agent_outs=agent_outs_stacked, logits=logits, states=actor_state, # we only keep the final actor state ) # Update training state training_state = DiscoTrainingState( learner_state=learner_state, actor_state=actor_state, update_rule_params=update_rule_params, rng=rng, ) return rollout, training_state def main(): # ===== Setup ===== print('='*60) print('DiscoRL Training on CartPole') print('='*60) # Configuration batch_size = 4 trajectory_length = 32 num_iterations = 50 # number of training steps learning_rate = 1e-4 # Create environment print('\nCreating environment...') env = DiscoCartPoleEnv( batch_size=batch_size, max_steps=500, ) single_obs_spec = env.single_observation_spec() single_act_spec = env.single_action_spec() print(f' Obs spec: {single_obs_spec}') print(f' Act spec: {single_act_spec}') # Create agent with Disco103 settings print('\nCreating DiscoRL agent...') agent_settings = disco_agent.get_settings_disco() agent = disco_agent.Agent( single_observation_spec=single_obs_spec, single_action_spec=single_act_spec, agent_settings=agent_settings, batch_axis_name=None, ) # Initialize agent state rng = jax.random.PRNGKey(42) rng, subkey = jax.random.split(rng) learner_state = agent.initial_learner_state(subkey) rng, subkey = jax.random.split(rng) actor_state = agent.initial_actor_state(subkey) # Initialize meta params from update rule (random initialization) print('\nInitializing meta-params...') rng, subkey = jax.random.split(rng) update_rule_params, _ = agent.update_rule.init_params(subkey) # ===== Training Loop ===== print('\n' + '='*60) print('Starting Training') print('='*60) training_state = DiscoTrainingState( learner_state=learner_state, actor_state=actor_state, update_rule_params=update_rule_params, rng=rng, ) cumulative_rewards = [] for iteration in range(num_iterations): # Collect trajectory rollout, training_state = rollout_trajectory( agent=agent, env=env, training_state=training_state, trajectory_length=trajectory_length, ) # Compute average reward in this rollout avg_reward = jnp.mean(rollout.rewards) cumulative_rewards.append(float(avg_reward)) # Learner step (update network parameters using meta-net guidance) rng, subkey = jax.random.split(training_state.rng) new_learner_state, new_actor_state, log_dict = agent.learner_step( rng=subkey, rollout=rollout, learner_state=training_state.learner_state, agent_net_state=training_state.actor_state, update_rule_params=training_state.update_rule_params, is_meta_training=False, # We use pre-trained meta-net, no meta-training ) training_state = DiscoTrainingState( learner_state=new_learner_state, actor_state=new_actor_state, update_rule_params=training_state.update_rule_params, rng=rng, ) # Logging if (iteration + 1) % 10 == 0: print(f'Iter {iteration+1:4d}: ' f' avg_reward={avg_reward:7.3f} ' f' total_loss={log_dict.get("total_loss", 0):7.5f}') # Checkpointing (save every 50 iterations) if (iteration + 1) % 50 == 0: checkpoint_path = os.path.join( repo_root, 'models', 'disco_cartpole', f'checkpoint_{iteration+1}.npz' ) os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True) # Save learner params (simplified; real implementation would also save state) np.savez( checkpoint_path, params=jax.tree.map(np.asarray, training_state.learner_state.params), ) print(f' Saved checkpoint to {checkpoint_path}') # ===== Final Results ===== print('\n' + '='*60) print('Training Complete') print('='*60) print(f'Final avg reward (last 10 iters): {np.mean(cumulative_rewards[-10:]):.3f}') print(f'Max avg reward seen: {np.max(cumulative_rewards):.3f}') # Save final model final_path = os.path.join( repo_root, 'models', 'disco_cartpole', 'final_agent.npz' ) os.makedirs(os.path.dirname(final_path), exist_ok=True) np.savez( final_path, params=jax.tree.map(np.asarray, training_state.learner_state.params), ) print(f'Saved final agent to {final_path}') if __name__ == '__main__': main()