Skip to content

T1 — First rollout (RT 2D)

Install orbitalgym, run a planar (2D) HCW rollout with zero control on both sides, and read three pieces of data off the resulting Trajectory. A companion notebook lives at examples/workflow_2d_rt.ipynb.

Install

git clone https://github.com/sisl/OrbitalGym.git
cd OrbitalGym
uv sync --extra dev

Imports

    import jax

    from orbitalgym import OrbitalGymEnv, SingleAgentView, make_lady_bandit_guard
    from orbitalgym.policies import ZeroControl
    from orbitalgym.registry import DynamicsKey, StateComponentKey
    from orbitalgym.rollout import rollout_single_agent

Build a 2D Lady-Bandit-Guard scenario

    cfg = make_lady_bandit_guard(
        n_guards=1,
        n_bandits=1,
        truth_dynamics=DynamicsKey.HCW_RT,
        policy_dynamics=DynamicsKey.HCW_RT,
        guard_components=(StateComponentKey.RT, StateComponentKey.MASS),
        bandit_components=(StateComponentKey.RT,),
        max_horizon_s=2000.0,
        seed=0,
    )

HCW_RT is the planar 4-state Clohessy–Wiltshire dynamics. Vehicle state is [r, θ, ṙ, θ̇] — radial and along-track position and velocity in the rotating reference-orbit frame. The full 6-state RTN form is what T2 will use.

Each side carries an explicit list of state components that must match the chosen dynamics — RT is required by HCW_RT, and the guard additionally tracks MASS so propellant is bookkept across impulses.

Roll the episode out

    env = OrbitalGymEnv(cfg)
    view = SingleAgentView(env)

    guard_policy = ZeroControl(command_cls=env.guard_command_cls, n_vehicles=cfg.n_guards)
    traj = rollout_single_agent(
        view,
        guard_policy,
        lambda c, s, k: None,
        jax.random.PRNGKey(cfg.seed),
        n_steps=cfg.max_steps,
    )

rollout_single_agent compiles down to one jax.lax.scan call. The opposing side (bandit) is driven internally by cfg.bandit_policy — defaulting to ZeroControl.

Inspect the trajectory

    total_reward = float(traj.sides.guard.reward.sum())
    n_steps_actual = int(traj.episode_done.argmax()) + 1
    state_shape = traj.env_state.guards.rt.shape  # (T, N_g, 4)
  • traj.sides.guard.reward.sum() is the cumulative guard reward — for make_lady_bandit_guard() this is LbgZeroSumReward (a per-side potential over the engagement geometry, plus terminal events at catch / breach). With both sides at zero thrust neither side scores a terminal event, so the cumulative return is the shaping term, which telescopes to the change in that side's potential over the drift.
  • traj.episode_done.argmax() + 1 is the step at which the episode terminated. With no terminal event triggering, this equals cfg.max_steps.
  • traj.env_state.guards.rt has shape (T, N_g, 4) — see In depth → Symmetric core for the full axis convention.

Next

→ T2 — Build an RTN scenario scales up to 3D dynamics, adds vehicles, and renders trajectories you can spin in matplotlib.