TargetGym
21 JAX environments for setpoint tracking,
with tuned PID and MPC baselines.
Reach the target. Then hold it.
One example task from each family, under PID control.
TargetGym provides reinforcement learning environments for target MDPs: tasks where the objective is to reach a setpoint and hold it against disturbances, rather than to reach a goal state once. The environments model real plants, including an A320-like aircraft, a glass furnace, a nuclear reactor, a cement kiln, a grid battery and a wind turbine.
- Baselines included. Every environment ships a tuned PID; 20 of 21 also
ship an MPC with full state access, which serves as an upper bound. Both are
recorded over ten seeds in
src/target_gym/data/baseline_returns.json. - Validated physics. Each environment carries a
PHYSICS.mdwith a sourced parameter table, published validation targets asserted by tests, and its documented approximations. - JAX throughout.
jit,vmapandscancompatible, end-to-end GPU, 0.6 M to 700 M steps/s on CPU depending on the plant. - gymnax API, with a Gymnasium wrapper for non-JAX libraries and a JaxMARL interface for the multi-agent patrol task.
Learned-policy results are not published yet. The measurement protocol is defined in docs/rl-protocol.md.
Installation
pip install target-gym
Quickstart
Also available as a Colab notebook.
import jax
import numpy as np
from target_gym import Plane
env = Plane()
pid = env.make_pid() # the shipped baseline, tuned
key = jax.random.PRNGKey(0)
obs, state = env.reset(key)
total = 0.0
for _ in range(env.default_params.max_steps_in_episode):
action = pid(np.asarray(obs))
obs, state, reward, terminated, truncated, _ = env.step(key, state, action)
total += float(reward)
if terminated or truncated:
break
print("PID return:", total)
reset and step follow the
gymnax API and take an optional
params; each environment exports its parameter class (PlaneParams, and so
on) for custom configurations. Every environment also exposes make_pid(),
make_mpc() and save_video().
Non-JAX libraries, e.g. stable-baselines3
End-to-end GPU requires a JAX-based library.
# doc: skip (trains for 10 000 steps; tests/plane/test_agent.py covers this path)
from target_gym import GymnasiumPlane
from stable_baselines3 import SAC
env = GymnasiumPlane()
model = SAC("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=10_000, log_interval=4)
obs, info = env.reset()
while True:
action, _states = model.predict(obs, deterministic=True)
obs, reward, terminated, truncated, info = env.step(action)
if terminated or truncated:
break
Comparing against the baselines
For most environments the defaults are the scored configuration, so a plain
rollout is comparable to the published numbers. The exceptions are the plants
that host several task variants: PlaneParams is shared by plane,
plane_sine and plane_energy, which are scored over 280, 480 and 1200 steps,
so one class cannot default to all three. EnvSpec.test_params carries the
per-variant settings, and spec.make_test_params() resolves them:
import numpy as np
from target_gym.provenance import load_recorded_baselines
from target_gym.registry import REGISTRY
from target_gym.runners.runners import baseline_policy, rollout
spec = REGISTRY["plane"]
params = spec.make_test_params() # the scored configuration
pid = baseline_policy(spec, "pid", params)
print("PID:", float(np.sum(rollout(spec, params, pid, seed=0)[2])))
print("MPC:", load_recorded_baselines()["plane"]["mpc_returns"][0])
The PID reads obs only, as a plant controller does. The MPC reads the full
state, including quantities the observation withholds, which is what makes it
an upper bound rather than a peer; docs/baselines.md
quantifies the resulting advantage.
Vectorised rollouts, the registry API, the patrol interface and the wind model are covered in docs/getting-started.md.
Environments
| Family | Count | Environments |
|---|---|---|
| Aircraft | 9 | A320-like 2D aircraft on three reference patterns; four 3D path-following tasks; two formation-patrol variants |
| Process control | 5 | CSTR, first-order lag, four-tank, pH neutralisation, binary distillation |
| Industrial / energy | 5 | Glass furnace, nuclear reactor, building HVAC, boiler drum, cement kiln |
| Renewable energy | 2 | Wind turbine, grid battery |
Full environment reference → with observation and action shapes, tracked variables, baseline returns and physics contracts.
The CSTR, first-order and four-tank models are adapted from
PC-gym and checked against its source
term by term, as each PHYSICS.md provenance line records.
The two patrol environments are multi-agent: wingmen hold a slot on a lead flying its own route, so the reference is another aircraft and collision ends the episode.
Environments span six difficulty tiers, graded on dynamics (linearity, coupling, stiffness) and on the RL side (dimensionality, horizon, partial observability), from a first-order lag at tier 1 to the cement kiln and the multi-agent patrol at tier 6. See the complexity ladder.
Each environment renders a control-room dashboard: plant schematic, gauges with limit and setpoint markers, strip charts, and explicit marking of quantities the controller cannot measure. See the rendering guide.
Why setpoint tracking
Holding a setpoint indefinitely exposes failure modes that episodic goal-reaching does not:
| Property | In the suite | Why it is hard to learn |
|---|---|---|
| Irrecoverable states | A drum that carries water into the turbine, a reactor past runaway, a kiln gone cold | Exploration that reaches them ends the episode permanently |
| Deep partial observability | The furnace hides 6 of 9 states, the reactor 7 of 11, the kiln 64 behind 8 measurements | The policy has to infer what it cannot measure |
| Wrong-way-first response | Opening the steam valve makes drum level rise before it falls, as steam bubbles expand | A controller following the immediate trend pushes the loop the wrong way |
| Transport delay | Half the kiln's response to a fuel change arrives a 25-minute residence time later | Credit assignment spans hundreds of steps |
| Multi-timescale dynamics | Millisecond neutronics against hour-long xenon; sub-second flame gas against 30-hour glass residence | One control interval cannot serve both ends |
| Finite budgets | A battery spends charge to follow dispatch and then cannot follow it | Tracking now is priced against tracking later |
Also modelled: actuator lag, competing objectives, and scheduled setpoints that reward anticipation (the building's night setback, the furnace's crown schedule, the battery's dispatch blocks). Aircraft fly in steady wind, altitude shear and Ornstein-Uhlenbeck turbulence, unobservable by default.
Baselines
Every environment ships a tuned PID; 20 of 21 also ship an MPC. The exception is
patrol_bearing_only, which withholds the slot error a planner would read.
Controller structure is chosen per plant:
- Boiler drum: three-element control, so feedwater tracks measured steam flow and shrink-and-swell cannot mislead the level loop.
- Cement kiln: cascade, because integral action on a 25-minute-old measurement oscillates at the delay period.
- Four-tank: crossed loops, since the negative RGA element makes the diagonal pairing unstable.
Three MPC implementations are used: CasADi/IPOPT where a symbolic model exists, gradient-based planning through the JAX dynamics elsewhere, and cross-entropy sampling for the cement kiln. Solver convergence is recorded alongside every result.
Each baseline must beat the best constant action on its environment, a deliberately low bar that a mis-wired controller fails. Weak baselines are documented as such on their environment page.
A PID losing on a task says something about that task, not about PID control. These environments are selected for problems where anticipation pays. Where reacting to the reference and the disturbances is sufficient, a PID is optimal or close enough that the difference does not appear in a return. Two environments were corrected after measurement showed exactly this: a battery whose dispatch signal was so noisy that no controller could exceed 0.43 of the reward ceiling, and a patrol follower tracking a lead at one fixed turn rate, which a single feedforward term cancels.
docs/baselines.md covers the MPC implementations, tuning and caching, solver reporting and per-environment coverage.
Physics validation
Each environment carries a PHYSICS.md stating what it models and where that
holds, a parameter table citing or deriving every constant (flagged
TUNED - not sourced when neither applies), the published numbers it must
reproduce, and its known deviations.
Tests assert consequences rather than formulas: ISA table values, L/D ratios, thermal time constants, energy balances, equilibria. A test that recomputes the implementation's own expression would pass on a wrong one.
All 21 environments are covered by fifteen contracts, since aircraft variants
share a plant. A shared conformance suite additionally checks determinism, PRNG
handling, jit/vmap/scan compatibility and numerical health over full
episodes.
docs/PHYSICS_METHODOLOGY.md documents the method and lists what each model is validated against.
Documentation
| Getting started | Episodes, vectorised rollouts, the registry, Gymnasium |
| Target MDPs | The formal setting |
| Environment reference | All 21: shapes, tracked variables, baselines, contracts |
| Public API | Stable and provisional surface |
| Baselines | PID and MPC controllers, tuning, solver reporting |
| RL protocol | Measuring a learned policy |
| Reward shaping | The tracking reward and why it has that shape |
| Complexity ladder | Six tiers, for curriculum use |
| Rendering | Dashboards, toolkits, regenerating media |
| Throughput | Steps per second per environment |
| Physics methodology | Sourcing, validation, bounds |
| Model review checklist | Thirteen checks for any plant model |
| Testing | Suite organisation |
Rendered with search and navigation at yannberthelot.github.io/TargetGym. The links above point at the Markdown in this repository, which reads on GitHub and is rewritten for the site at build time.
Related projects
- gymnax: the JAX environment API TargetGym implements.
- Gymnasium: supported through a wrapper for non-JAX libraries.
- PC-gym: process-control environments; source of the CSTR, first-order and four-tank models.
- safe-control-gym: closest in intent, comparing classical control, MPC and RL on shared tasks. It covers more controllers, TargetGym covers more plants.
- JaxMARL: the multi-agent API the patrol formation task follows.
Roadmap
Rewards, physics, baselines and the measurement protocol are settled. Outstanding before 1.0: published learned-policy results and hosted documentation. Known gaps are recorded rather than omitted.
Contributing
Bug reports, new environments, improved baselines and physics corrections are welcome.
git clone https://github.com/YannBerthelot/TargetGym.git
cd TargetGym
uv sync --group dev
make ci # ruff, black --check, docs, fast tests
Further targets: make test, make test-all, make figures, make videos,
make tuning.
Adding an environment means registering an EnvSpec, which inherits every
shared check, and writing a PHYSICS.md sourcing its numbers.
CONTRIBUTING.md has the details.
Citation
@misc{targetgym2025,
title = {TargetGym: Reinforcement Learning Environments for Target MDPs},
author = {Yann Berthelot},
year = {2025},
url = {https://github.com/YannBerthelot/TargetGym},
note = {Lightweight physics-based RL environments for aircraft, process control, and industrial systems}
}
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file target_gym-0.6.1.tar.gz.
File metadata
- Download URL: target_gym-0.6.1.tar.gz
- Upload date:
- Size: 386.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ea761e6da7c16c7a59b4727ddcb795c47482e729ac2660b3157da00dbdefe09
|
|
| MD5 |
97768b569f52fb81475cf6bec76e8640
|
|
| BLAKE2b-256 |
263a7563494f5a1d9a18c5e92ac108c8d5fb4a593d746aad1c3db591e9ddc310
|
File details
Details for the file target_gym-0.6.1-py3-none-any.whl.
File metadata
- Download URL: target_gym-0.6.1-py3-none-any.whl
- Upload date:
- Size: 441.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f7ceddb70dcaa4c4911ec725bd2cab21b5a8d9c3fa821614d4f649f599114c9
|
|
| MD5 |
13b5030a820c1f6f8bf52095da38ea78
|
|
| BLAKE2b-256 |
4cf468a896fbb9f560ec7f845604544a422049be89d80c1b22f5cd0b452f261c
|