ezppo 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,70 @@
1
+ # This workflow will upload a Python Package to PyPI when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ release:
13
+ types: [published]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ release-build:
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.x"
28
+
29
+ - name: Build release distributions
30
+ run: |
31
+ # NOTE: put your own distribution build steps here.
32
+ python -m pip install build
33
+ python -m build
34
+
35
+ - name: Upload distributions
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: release-dists
39
+ path: dist/
40
+
41
+ pypi-publish:
42
+ runs-on: ubuntu-latest
43
+ needs:
44
+ - release-build
45
+ permissions:
46
+ # IMPORTANT: this permission is mandatory for trusted publishing
47
+ id-token: write
48
+
49
+ # Dedicated environments with protections for publishing are strongly recommended.
50
+ # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
51
+ environment:
52
+ name: pypi
53
+ # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
54
+ # url: https://pypi.org/p/YOURPROJECT
55
+ #
56
+ # ALTERNATIVE: if your GitHub Release name is the PyPI project version string
57
+ # ALTERNATIVE: exactly, uncomment the following line instead:
58
+ url: https://pypi.org/project/ezppo//${{ github.event.release.name }}
59
+
60
+ steps:
61
+ - name: Retrieve release distributions
62
+ uses: actions/download-artifact@v4
63
+ with:
64
+ name: release-dists
65
+ path: dist/
66
+
67
+ - name: Publish release distributions to PyPI
68
+ uses: pypa/gh-action-pypi-publish@release/v1
69
+ with:
70
+ packages-dir: dist/
ezppo-0.0.0/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ # Virtual environments
2
+ .venv/
3
+ venv/
4
+ env/
5
+ ppoVenv/
6
+ testingVenv/
7
+
8
+ # Python
9
+ __pycache__/
10
+ *.py[cod]
11
+ *.egg-info/
12
+ dist/
13
+ build/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Checkpoints
24
+ checkpoints/
25
+
26
+ # Old files backup
27
+ old/
ezppo-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: ezppo
3
+ Version: 0.0.0
4
+ Summary: A simple PPO library for Discrete and Continuous environments
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: torch
8
+ Requires-Dist: gymnasium
9
+ Requires-Dist: numpy
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest; extra == "dev"
12
+
13
+ # EzPPO
14
+
15
+ A simple, unified PPO (Proximal Policy Optimization) library for PyTorch. Supports both **discrete** and **continuous** action spaces.
16
+
17
+ ## Features
18
+
19
+ - **Unified API**: Same training loop for discrete and continuous
20
+ - **Observation normalization**: Online running statistics
21
+ - **GAE (Generalized Advantage Estimation)**: For stable advantage estimation
22
+ - **Clipped surrogate objective**: Standard PPO clipping
23
+ - **Mini-batch training**: With random shuffling
24
+ - **Gradient clipping**: Prevents exploding gradients
25
+ - **Checkpoint save/load**: Resume training easily
26
+ - **Multi-environment support**: Parallel environments built-in
27
+ - **GPU support**: Automatic device management
28
+
29
+ ## Quick Start
30
+
31
+ ### Discrete Actions (CartPole)
32
+
33
+ ```python
34
+ from ezppo import DiscretePPOAgent
35
+ import gymnasium as gym
36
+
37
+ agent = DiscretePPOAgent(state_dim=4, action_dim=2)
38
+ env = gym.make("CartPole-v1")
39
+
40
+ state, _ = env.reset()
41
+ action, log_prob = agent.act(state) # Stochastic action
42
+ # action = agent.act_deterministic(state) # Greedy action (for eval)
43
+ ```
44
+
45
+ ### Continuous Actions (Pendulum)
46
+
47
+ ```python
48
+ from ezppo import ContinuousPPOAgent
49
+ import gymnasium as gym
50
+
51
+ agent = ContinuousPPOAgent(state_dim=3, action_dim=1)
52
+ env = gym.make("Pendulum-v1")
53
+
54
+ state, _ = env.reset()
55
+ action, log_prob, raw_action = agent.act(state)
56
+ # action = agent.act_deterministic(state)[0] # Mean action (for eval)
57
+ ```
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install torch gymnasium numpy
63
+ ```
64
+
65
+ ## Architecture
66
+
67
+ ```
68
+ ezppo/
69
+ ├── __init__.py # Public API
70
+ ├── networks.py # DiscretePPO, ContinuousPPO, BasePPO
71
+ ├── normalizer.py # RunningNormalizer
72
+ └── agents.py # DiscretePPOAgent, ContinuousPPOAgent
73
+
74
+ examples/
75
+ ├── discrete_cartpole.py
76
+ └── continuous_pendulum.py
77
+ ```
78
+
79
+ ## Training Loop Template
80
+
81
+ ```python
82
+ trajectory = []
83
+ max_trajectory = 2048
84
+
85
+ for episode in range(num_episodes):
86
+ state, _ = env.reset()
87
+ done = False
88
+
89
+ while not done:
90
+ action, log_prob = agent.act(state)
91
+ next_state, reward, terminated, truncated, _ = env.step(action.item())
92
+ done = terminated or truncated
93
+
94
+ trajectory.append((state, action.item(), reward, log_prob.item(),
95
+ next_state, terminated))
96
+ state = next_state
97
+
98
+ if len(trajectory) >= max_trajectory:
99
+ agent.train(num_epochs=4, batch_size=64)
100
+ trajectory.clear()
101
+ ```
102
+
103
+ ## API Reference
104
+
105
+ ### Networks
106
+
107
+ | Class | Purpose |
108
+ |-------|---------|
109
+ | `BasePPO` | Base class with configurable architecture |
110
+ | `DiscretePPO` | Discrete actions → logits output |
111
+ | `ContinuousPPO` | Continuous actions → mean + learnable log_std |
112
+
113
+ ### Agents
114
+
115
+ | Class | Purpose |
116
+ |-------|---------|
117
+ | `DiscretePPOAgent` | Discrete action space |
118
+ | `ContinuousPPOAgent` | Continuous action space |
119
+
120
+ ### Key Methods
121
+
122
+ **Agent.act(state)** → `(action, log_prob)` or `(action, log_prob, raw_action)`
123
+ **Agent.act_deterministic(state)** → Deterministic action (for evaluation)
124
+ **Agent.train(num_epochs=4, batch_size=64)** → Train on trajectory
125
+ **Agent.store(packet)** → Add transition to trajectory
126
+ **Agent.clear()** → Clear trajectory buffer
127
+ **Agent.save(path)** → Save checkpoint
128
+ **Agent.load(path)** → Load checkpoint
129
+
130
+ ## Configuration
131
+
132
+ ```python
133
+ agent = DiscretePPOAgent(
134
+ state_dim=4, action_dim=2,
135
+ architecture={ # Custom network architecture
136
+ "actor": [state_dim, 64, 64, action_dim],
137
+ "critic": [state_dim, 64, 64, 1]
138
+ },
139
+ lr=3e-4, gamma=0.99,
140
+ gae_lambda=0.95, epsilon=0.2,
141
+ critic_coef=0.5, entropy_coef=0.01,
142
+ num_envs=1, trajectory_size=2048,
143
+ device="cpu"
144
+ )
145
+ ```
146
+
147
+ ## License
148
+
149
+ MIT
ezppo-0.0.0/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # EzPPO
2
+
3
+ A simple, unified PPO (Proximal Policy Optimization) library for PyTorch. Supports both **discrete** and **continuous** action spaces.
4
+
5
+ ## Features
6
+
7
+ - **Unified API**: Same training loop for discrete and continuous
8
+ - **Observation normalization**: Online running statistics
9
+ - **GAE (Generalized Advantage Estimation)**: For stable advantage estimation
10
+ - **Clipped surrogate objective**: Standard PPO clipping
11
+ - **Mini-batch training**: With random shuffling
12
+ - **Gradient clipping**: Prevents exploding gradients
13
+ - **Checkpoint save/load**: Resume training easily
14
+ - **Multi-environment support**: Parallel environments built-in
15
+ - **GPU support**: Automatic device management
16
+
17
+ ## Quick Start
18
+
19
+ ### Discrete Actions (CartPole)
20
+
21
+ ```python
22
+ from ezppo import DiscretePPOAgent
23
+ import gymnasium as gym
24
+
25
+ agent = DiscretePPOAgent(state_dim=4, action_dim=2)
26
+ env = gym.make("CartPole-v1")
27
+
28
+ state, _ = env.reset()
29
+ action, log_prob = agent.act(state) # Stochastic action
30
+ # action = agent.act_deterministic(state) # Greedy action (for eval)
31
+ ```
32
+
33
+ ### Continuous Actions (Pendulum)
34
+
35
+ ```python
36
+ from ezppo import ContinuousPPOAgent
37
+ import gymnasium as gym
38
+
39
+ agent = ContinuousPPOAgent(state_dim=3, action_dim=1)
40
+ env = gym.make("Pendulum-v1")
41
+
42
+ state, _ = env.reset()
43
+ action, log_prob, raw_action = agent.act(state)
44
+ # action = agent.act_deterministic(state)[0] # Mean action (for eval)
45
+ ```
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install torch gymnasium numpy
51
+ ```
52
+
53
+ ## Architecture
54
+
55
+ ```
56
+ ezppo/
57
+ ├── __init__.py # Public API
58
+ ├── networks.py # DiscretePPO, ContinuousPPO, BasePPO
59
+ ├── normalizer.py # RunningNormalizer
60
+ └── agents.py # DiscretePPOAgent, ContinuousPPOAgent
61
+
62
+ examples/
63
+ ├── discrete_cartpole.py
64
+ └── continuous_pendulum.py
65
+ ```
66
+
67
+ ## Training Loop Template
68
+
69
+ ```python
70
+ trajectory = []
71
+ max_trajectory = 2048
72
+
73
+ for episode in range(num_episodes):
74
+ state, _ = env.reset()
75
+ done = False
76
+
77
+ while not done:
78
+ action, log_prob = agent.act(state)
79
+ next_state, reward, terminated, truncated, _ = env.step(action.item())
80
+ done = terminated or truncated
81
+
82
+ trajectory.append((state, action.item(), reward, log_prob.item(),
83
+ next_state, terminated))
84
+ state = next_state
85
+
86
+ if len(trajectory) >= max_trajectory:
87
+ agent.train(num_epochs=4, batch_size=64)
88
+ trajectory.clear()
89
+ ```
90
+
91
+ ## API Reference
92
+
93
+ ### Networks
94
+
95
+ | Class | Purpose |
96
+ |-------|---------|
97
+ | `BasePPO` | Base class with configurable architecture |
98
+ | `DiscretePPO` | Discrete actions → logits output |
99
+ | `ContinuousPPO` | Continuous actions → mean + learnable log_std |
100
+
101
+ ### Agents
102
+
103
+ | Class | Purpose |
104
+ |-------|---------|
105
+ | `DiscretePPOAgent` | Discrete action space |
106
+ | `ContinuousPPOAgent` | Continuous action space |
107
+
108
+ ### Key Methods
109
+
110
+ **Agent.act(state)** → `(action, log_prob)` or `(action, log_prob, raw_action)`
111
+ **Agent.act_deterministic(state)** → Deterministic action (for evaluation)
112
+ **Agent.train(num_epochs=4, batch_size=64)** → Train on trajectory
113
+ **Agent.store(packet)** → Add transition to trajectory
114
+ **Agent.clear()** → Clear trajectory buffer
115
+ **Agent.save(path)** → Save checkpoint
116
+ **Agent.load(path)** → Load checkpoint
117
+
118
+ ## Configuration
119
+
120
+ ```python
121
+ agent = DiscretePPOAgent(
122
+ state_dim=4, action_dim=2,
123
+ architecture={ # Custom network architecture
124
+ "actor": [state_dim, 64, 64, action_dim],
125
+ "critic": [state_dim, 64, 64, 1]
126
+ },
127
+ lr=3e-4, gamma=0.99,
128
+ gae_lambda=0.95, epsilon=0.2,
129
+ critic_coef=0.5, entropy_coef=0.01,
130
+ num_envs=1, trajectory_size=2048,
131
+ device="cpu"
132
+ )
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,98 @@
1
+ """
2
+ Continuous PPO example: Pendulum-v1
3
+
4
+ Trains a PPO agent to swing up and balance the pendulum.
5
+ """
6
+
7
+ import gymnasium as gym
8
+ import numpy as np
9
+ from ezppo import ContinuousPPOAgent
10
+
11
+
12
+ def main():
13
+ # Environment setup
14
+ env_id = "Pendulum-v1"
15
+ env = gym.make(env_id, render_mode="rgb_array")
16
+
17
+ state_dim = env.observation_space.shape[0]
18
+ action_dim = env.action_space.shape[0]
19
+
20
+ print(f"Training on {env_id}")
21
+ print(f"State dim: {state_dim}, Action dim: {action_dim}")
22
+ print(f"Action range: [{env.action_space.low[0]}, {env.action_space.high[0]}]")
23
+
24
+ # Create agent — performant hyperparams, normalization off for stability
25
+ agent = ContinuousPPOAgent(
26
+ state_dim=state_dim,
27
+ action_dim=action_dim,
28
+ architecture={
29
+ "actor": [state_dim, 256, 256, action_dim],
30
+ "critic": [state_dim, 256, 256, 1]
31
+ },
32
+ lr=3e-4,
33
+ gamma=0.99,
34
+ gae_lambda=0.95,
35
+ epsilon=0.2,
36
+ critic_coef=0.5,
37
+ entropy_coef=0.01,
38
+ num_envs=1,
39
+ trajectory_size=512,
40
+ device="cpu",
41
+ normalize_observations=False,
42
+ default_num_epochs=10,
43
+ default_batch_size=64
44
+ )
45
+
46
+ # Training loop
47
+ max_episodes = 5000
48
+ best_reward = -float("inf")
49
+
50
+ for episode in range(max_episodes):
51
+ state, info = env.reset()
52
+ total_reward = 0
53
+ done = False
54
+
55
+ while not done:
56
+ action, log_prob, raw_action = agent.act(state)
57
+ # Scale action to env range: tanh gives [-1,1], scale to action space
58
+ action_scaled = action * env.action_space.high[0]
59
+ next_state, reward, terminated, truncated, info = env.step(action_scaled)
60
+ done = terminated or truncated
61
+ total_reward += reward
62
+
63
+ agent.store((state, raw_action, reward, log_prob, next_state, terminated))
64
+ state = next_state
65
+
66
+ # Train when trajectory is full
67
+ if len(agent.trajectory) >= agent._trajectory_size:
68
+ agent.train()
69
+ agent.clear()
70
+
71
+ if total_reward > best_reward:
72
+ best_reward = total_reward
73
+
74
+ if (episode + 1) % 100 == 0:
75
+ print(f"Episode {episode+1}/{max_episodes} | Reward: {total_reward:.1f} | Best: {best_reward:.1f}")
76
+
77
+ # Save checkpoint
78
+ agent.save("checkpoints/pendulum_ppo", None)
79
+ print("Checkpoint saved!")
80
+
81
+ # Evaluation
82
+ print("\n--- Evaluation ---")
83
+ eval_env = gym.make(env_id, render_mode="human")
84
+ for eval_ep in range(5):
85
+ state, info = eval_env.reset()
86
+ total_reward = 0
87
+ done = False
88
+ while not done:
89
+ action = agent.act_deterministic(state)[0] # Get deterministic action
90
+ action_scaled = action * eval_env.action_space.high[0]
91
+ state, reward, terminated, truncated, info = eval_env.step(action_scaled)
92
+ done = terminated or truncated
93
+ total_reward += reward
94
+ print(f"Eval episode: {total_reward}")
95
+
96
+
97
+ if __name__ == "__main__":
98
+ main()
@@ -0,0 +1,94 @@
1
+ """
2
+ Discrete PPO example: CartPole-v1
3
+
4
+ Trains a PPO agent to balance the pole on a cart.
5
+ """
6
+
7
+ import gymnasium as gym
8
+ from ezppo import DiscretePPOAgent
9
+
10
+
11
+ def main():
12
+ # Environment setup
13
+ env_id = "CartPole-v1"
14
+ env = gym.make(env_id, render_mode="rgb_array")
15
+
16
+ state_dim = env.observation_space.shape[0]
17
+ action_dim = env.action_space.n
18
+
19
+ print(f"Training on {env_id}")
20
+ print(f"State dim: {state_dim}, Action dim: {action_dim}")
21
+
22
+ # Create agent — performant hyperparams based on old reference impl
23
+ agent = DiscretePPOAgent(
24
+ state_dim=state_dim,
25
+ action_dim=action_dim,
26
+ architecture={
27
+ "actor": [state_dim, 256, 256, action_dim],
28
+ "critic": [state_dim, 256, 256, 1]
29
+ },
30
+ lr=3e-4,
31
+ gamma=0.99,
32
+ gae_lambda=0.95,
33
+ epsilon=0.2,
34
+ critic_coef=0.5,
35
+ entropy_coef=0.001,
36
+ num_envs=1,
37
+ trajectory_size=512,
38
+ device="cpu",
39
+ normalize_observations=False,
40
+ default_num_epochs=10,
41
+ default_batch_size=64
42
+ )
43
+
44
+ # Training loop
45
+ max_episodes = 2000
46
+ best_reward = -float("inf")
47
+
48
+ for episode in range(max_episodes):
49
+ state, info = env.reset()
50
+ total_reward = 0
51
+ done = False
52
+
53
+ while not done:
54
+ action, log_prob = agent.act(state)
55
+ next_state, reward, terminated, truncated, info = env.step(action.item())
56
+ done = terminated or truncated
57
+ total_reward += reward
58
+
59
+ agent.store((state, action.item(), reward, log_prob.item(), next_state, terminated))
60
+ state = next_state
61
+
62
+ # Train when trajectory is full
63
+ if len(agent.trajectory) >= agent._trajectory_size:
64
+ agent.train()
65
+ agent.clear()
66
+
67
+ # Track best
68
+ if total_reward > best_reward:
69
+ best_reward = total_reward
70
+
71
+ if (episode + 1) % 100 == 0:
72
+ print(f"Episode {episode+1}/{max_episodes} | Reward: {total_reward:.1f} | Best: {best_reward:.1f}")
73
+
74
+ # Save checkpoint
75
+ agent.save("checkpoints/cartpole_ppo", "checkpoints/cartpole_ppo_norm")
76
+ print("Checkpoint saved!")
77
+
78
+ # Evaluation
79
+ print("\n--- Evaluation ---")
80
+ eval_env = gym.make(env_id, render_mode="human")
81
+ for eval_ep in range(5):
82
+ state, info = eval_env.reset()
83
+ total_reward = 0
84
+ done = False
85
+ while not done:
86
+ action = agent.act_deterministic(state)
87
+ state, reward, terminated, truncated, info = eval_env.step(action)
88
+ done = terminated or truncated
89
+ total_reward += reward
90
+ print(f"Eval episode: {total_reward}")
91
+
92
+
93
+ if __name__ == "__main__":
94
+ main()
@@ -0,0 +1,86 @@
1
+ """
2
+ Evaluate a trained Discrete PPO checkpoint on CartPole-v1.
3
+
4
+ Usage:
5
+ python eval_cartpole.py --checkpoint checkpoints/cartpole_ppo
6
+ """
7
+
8
+ import argparse
9
+ import gymnasium as gym
10
+ import numpy as np
11
+ from ezppo import DiscretePPOAgent
12
+
13
+
14
+ def main():
15
+ parser = argparse.ArgumentParser(description="Evaluate a trained PPO checkpoint on CartPole-v1")
16
+ parser.add_argument("--checkpoint", type=str, default="checkpoints/cartpole_ppo",
17
+ help="Path to the saved model checkpoint (without extension)")
18
+ parser.add_argument("--episodes", type=int, default=10,
19
+ help="Number of evaluation episodes")
20
+ args = parser.parse_args()
21
+
22
+ # Environment setup
23
+ env_id = "CartPole-v1"
24
+ env = gym.make(env_id, render_mode="human")
25
+
26
+ state_dim = env.observation_space.shape[0]
27
+ action_dim = env.action_space.n
28
+
29
+ print(f"Evaluating on {env_id}")
30
+ print(f"State dim: {state_dim}, Action dim: {action_dim}")
31
+
32
+ # Create agent — matches the hyperparams used in training
33
+ agent = DiscretePPOAgent(
34
+ state_dim=state_dim,
35
+ action_dim=action_dim,
36
+ architecture={
37
+ "actor": [state_dim, 256, 256, action_dim],
38
+ "critic": [state_dim, 256, 256, 1]
39
+ },
40
+ lr=3e-4,
41
+ gamma=0.99,
42
+ gae_lambda=0.95,
43
+ epsilon=0.2,
44
+ critic_coef=0.5,
45
+ entropy_coef=0.001,
46
+ num_envs=1,
47
+ trajectory_size=512,
48
+ device="cpu",
49
+ normalize_observations=False,
50
+ default_num_epochs=10,
51
+ default_batch_size=64
52
+ )
53
+
54
+ # Load checkpoint — normalizer path only needed if normalization was enabled during training
55
+ agent.load(args.checkpoint)
56
+ print(f"Loaded checkpoint from: {args.checkpoint}\n")
57
+
58
+ # Evaluation
59
+ rewards = []
60
+ lengths = []
61
+ print(f"--- Evaluation ({args.episodes} episodes) ---")
62
+ for ep in range(args.episodes):
63
+ state, info = env.reset()
64
+ total_reward = 0
65
+ done = False
66
+ steps = 0
67
+ while not done:
68
+ action = agent.act_deterministic(state)
69
+ state, reward, terminated, truncated, info = env.step(action)
70
+ done = terminated or truncated
71
+ total_reward += reward
72
+ steps += 1
73
+ rewards.append(total_reward)
74
+ lengths.append(steps)
75
+ print(f" Episode {ep+1}/{args.episodes}: reward={total_reward:.1f}, steps={steps}")
76
+
77
+ print(f"\nAverage reward: {np.mean(rewards):.1f} +/- {np.std(rewards):.1f}")
78
+ print(f"Average length: {np.mean(lengths):.1f} +/- {np.std(lengths):.1f}")
79
+ print(f"Best reward: {np.max(rewards):.1f}")
80
+ print(f"Worst reward: {np.min(rewards):.1f}")
81
+ success_rate = np.mean([r >= 500 for r in rewards]) * 100
82
+ print(f"Success rate (reward >= 500): {success_rate:.0f}%")
83
+
84
+
85
+ if __name__ == "__main__":
86
+ main()