Skip to content

Gallery

Every named, importable policy that ships with orbitalgym. Use a class directly, or read its source as a starting point for your own.

Roster

Class Role Best in Summary
ZeroControl Both All Always emits zero — the baseline default.
UniformRandomDiscretePolicy Both All Uniform sample from a discrete Δv grid. Default MCTSPolicy.opponent_model.
MCTSPolicy Both All JAX-native Gumbel MuZero / PUCT search via mctx.
LeadInterceptPursuer Heuristic PE, LBG Closes on opponent's predicted next-step position.
OrthogonalEvader Heuristic PE Thrusts perpendicular to relative velocity.
SunTrackerBlocker Heuristic SB Maintains Sun-line geometry vs. opponent.
GlideslopeToLady Heuristic LBG Saturation-aware glideslope to the lady plus Gaussian repulsion from opponents.
GlideslopeIntercept Heuristic LBG, PE Saturation-aware glideslope onto the nearest opponent's state.
PhasedBandit Heuristic LBG Coast, transfer onto a standoff ring, hold there for free, then commit to the lady.
JitteredPolicy Heuristic (wrapper) All Wraps any policy and adds Gaussian jitter.
HeuristicWithFallbackPolicy Controlled (wrapper) All Confidence-gated routing between primary and fallback.
CompositeActionPolicy Controlled (wrapper) All Sums actions from a base and offset policy.

Importing

from orbitalgym.policies import UniformRandomDiscretePolicy, ZeroControl
from orbitalgym.policies.mcts import MCTSPolicy
from orbitalgym.policies.heuristic import (
    GlideslopeIntercept,
    GlideslopeToLady,
    LeadInterceptPursuer,
    OrthogonalEvader,
    PhasedBandit,
    SunTrackerBlocker,
    JitteredPolicy,
)
from orbitalgym.policies.controlled import (
    HeuristicWithFallbackPolicy,
    CompositeActionPolicy,
)

Class reference

ZeroControl

orbitalgym.policies.zero.ZeroControl dataclass

ZeroControl(command_cls: Any = None, n_vehicles: int = 0)

Always emits the identity Command (zeros for every component).

Env-populated init knobs (filled by OrbitalGymEnv via dataclasses.replace): command_cls: the per-side Command class (built by build_command_class) n_vehicles: number of vehicles on this side

Replaces the previous (n_vehicles, action_dim) raw-array contract.

UniformRandomDiscretePolicy

orbitalgym.policies.uniform_random.UniformRandomDiscretePolicy dataclass

UniformRandomDiscretePolicy(
    action_grid: Array,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Uniform-random over a (A, dv_dim) discrete action grid, per vehicle.

Env-populated init knobs (filled by OrbitalGymEnv via dataclasses.replace): command_cls: per-side Command class n_vehicles: number of vehicles on this side

The action_grid row count is the discrete action-space size; rows are Δv vectors whose dimensionality is padded to the side's command dv_dim if needed (e.g. 2-D RT grid into a 3-D RTN command).

MCTSPolicy

See In depth → MCTS policy for the full walkthrough; the API entry below is the bare class reference.

orbitalgym.policies.mcts.MCTSPolicy dataclass

MCTSPolicy(
    env_model: POMDPAdapter,
    side: Side,
    action_grid: Array,
    opponent_model: Any,
    opponent_action_grid: Array,
    num_simulations: int = 32,
    max_depth: int | None = None,
    leaf_value_fn: Callable[[Array], Array] | None = None,
    discount: float | None = None,
    variant: Literal[
        "muzero", "gumbel_muzero"
    ] = "gumbel_muzero",
    n_vehicles: int = 0,
    command_cls: Any = None,
    coordination: Literal["joint", "independent"] = "joint",
    teammate_model: Any = None,
    opponent_schedule: Any = None,
    max_num_considered_actions: int = 16,
)

JAX-native Gumbel MuZero / PUCT search backed by mctx.

See module docstring for the joint vs independent coordination modes.

LeadInterceptPursuer

orbitalgym.policies.heuristic.lead_intercept.LeadInterceptPursuer dataclass

LeadInterceptPursuer(
    max_dv_mps: float = 0.05,
    dt: float = 10.0,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Bandit (or guard) policy that closes on the opponent.

Assumes the obs is a FullObservation flat layout in 1v1 RTN: [own_rtn(6), opp_rtn(6)]. For other observation channels, wrap in a LeadInterceptPursuer-shaped adapter that reconstructs the same own/opp split.

OrthogonalEvader

orbitalgym.policies.heuristic.evader.OrthogonalEvader dataclass

OrthogonalEvader(
    max_dv_mps: float = 0.05,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Evades an approaching opponent by thrusting perpendicular to relative velocity.

Assumes obs layout [own_rtn(6), opp_rtn(6)]. Action lies in the orthogonal complement of the relative velocity vector.

SunTrackerBlocker

orbitalgym.policies.heuristic.sun_tracker.SunTrackerBlocker dataclass

SunTrackerBlocker(
    sun_dir_rtn: Array = _default_sun_dir(),
    max_dv_mps: float = 0.05,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Sun-line-aware heuristic blocker.

Assumes obs layout [own_rtn(6), opp_rtn(6)]. Thrusts along the Sun-direction line, oriented by the sign of the opponent's projection onto that line (so the thrust closes on the Sun-side of the opponent).

GlideslopeToLady

Both glideslope policies command the closing speed as the smallest of three terms: a linear glideslope rho / slope_s + arrival_mps, the braking curve sqrt(2 a_brake rho) the per-step budget can shed, and the sustainable speed hcw_fraction * max_dv_mps / (2 n dt). The last term exists because the Coriolis coupling of HCW dynamics charges about 2 n v dt of impulse per step to hold a relative velocity v that is not natural motion, so a vehicle that commands more than its budget can hold walks off the line of sight and the range grows instead of closing.

max_dv_mps / (2 n dt) bounds the Coriolis term alone, not the whole cost of the approach. The 3 n^2 x gradient term, the rotation of the line of sight as the range closes, and the finite step each add to or subtract from it, so the law spends only hcw_fraction of the bound and keeps the rest as margin. The default 0.5 is measured: over eight start phases on a 2 km ring offset along-track by 20 km and by 40 km, hcw_fraction = 1.0 arrives in six of the eight 20 km cases and none of the 40 km ones, 0.75 in eight and six, and 0.5 in all sixteen. At a 0.4545 m/s per-step cap, a 10 s step and n = 1.078e-3 rad/s, that leaves a sustainable speed of 10.5 m/s, which binds beyond about 2.4 km.

orbitalgym.policies.heuristic.glideslope.GlideslopeToLady dataclass

GlideslopeToLady(
    mean_motion: float,
    dt: float,
    max_dv_mps: float,
    slope_s: float,
    arrival_mps: float,
    brake_fraction: float,
    hcw_fraction: float,
    avoidance_gain_mps: float,
    avoidance_sigma_m: float,
    n_vehicles: int,
    n_opponents: int,
    state_dim: int,
    command_cls: Any,
)

Glide onto the lady at the origin and push away from nearby opponents.

The target state is the origin of the RTN frame, so the commanded velocity is the closing speed of the glideslope directed at the lady. Before the clip, a Gaussian repulsion avoidance_gain_mps * exp(-d^2 / (2 sigma^2)) per opponent, directed away from it, is added to the impulse.

The agent view is either a belief with mean (N_obs, N_total, d) or a flat observation of the same numbers; observer i reads its own state from cell (i, i) and opponents from columns N_obs onward. The flat-observation path requires either a single full-state channel (e.g. FullObservation or ConicalObservation) or a :class:~orbitalgym.observations.composite.CompositeObservation whose channels are all full-state and the same size — in which case the last channel is used. Position-only channels must go through a belief.

mean_motion sets the sustainable-speed term of the law through hcw_fraction.

GlideslopeIntercept

orbitalgym.policies.heuristic.glideslope.GlideslopeIntercept dataclass

GlideslopeIntercept(
    mean_motion: float,
    dt: float,
    max_dv_mps: float,
    slope_s: float,
    arrival_mps: float,
    brake_fraction: float,
    hcw_fraction: float,
    n_vehicles: int,
    n_opponents: int,
    state_dim: int,
    command_cls: Any,
)

Glide onto the nearest opposing vehicle, matching its velocity as the range closes.

Observer i picks the opposing vehicle nearest to it and takes that vehicle's in-plane state as the glideslope target, so the commanded velocity is the target's velocity plus the closing speed along the line of sight. The range and the relative speed therefore go to zero together.

The agent view is read exactly as :class:GlideslopeToLady reads it.

mean_motion sets the sustainable-speed term of the law through hcw_fraction.

PhasedBandit

A bandit on a schedule: it stays quiet for t_coast_s, spends fuel once to move onto a standoff ring of radial amplitude standoff_m, waits there for t_hold_s, and only then closes on the lady with the GlideslopeToLady command. Timing and information matter to the guards, and a game runs across several ground passes rather than resolving in one approach.

The hold is free because the standoff ring is a natural bounded relative orbit — the same 2:1 ellipse family RelativeEllipse samples for the initial conditions, with no secular drift — so a vehicle that reaches it coasts around it. Holding station at a fixed RTN point would have to be paid for every step. The transfer therefore targets the ellipse state, position and velocity, at the vehicle's current phase angle, and ends only once both are inside settle_tol_m and settle_tol_mps. The velocity tolerance sets how tight the hold is: a residual dv leaves an along-track drift of about 2 pi dv / (1.5 n) metres per orbit. Measured on a 2 km standoff ring approached from 3 km at a 0.4545 m/s per-step cap, over six start phases, settle_tol_mps=0.2 settles in 510 to 630 s and holds the amplitude within 47 to 75 percent of the standoff over an orbit; the default 0.02 settles in 750 to 840 s and keeps the hold within 3.4 to 7.7 percent.

Avoidance applies only in the commit phase. The transfer glides onto the ring with no repulsion term, so a nearby guard cannot push the vehicle off the target it is trying to reach.

Phases are per-vehicle and live in the policy state, so each bandit in a fleet runs its own schedule; transitions are jnp.where selects on traced values, so the policy runs under jit and vmap.

orbitalgym.policies.heuristic.phased_bandit.PhasedBandit dataclass

PhasedBandit(
    commit: GlideslopeToLady,
    mean_motion: float,
    dt: float,
    t_coast_s: float,
    standoff_m: float,
    t_hold_s: float,
    settle_tol_m: float,
    settle_tol_mps: float,
    t_transfer_max_s: float,
)

Run a bandit through a coast, a transfer to a standoff ring, a hold, and a commit.

The schedule makes timing and information matter: the bandit is quiet for t_coast_s, spends fuel once to move onto a ring of radial amplitude standoff_m, waits there for t_hold_s, and only then closes on the lady.

  1. coast — zero delta-v for t_coast_s seconds.
  2. transfer — glide onto the natural 2:1 ellipse of radial amplitude standoff_m at the vehicle's current phase angle (see :func:_ring_target), using :func:~orbitalgym.policies.heuristic.glideslope.glideslope_dv with the commit policy's slope_s and brake_fraction and an arrival speed of zero, so the vehicle arrives on the ring state rather than through it. Ends when the state is within settle_tol_m in position and settle_tol_mps in velocity of that target, or after t_transfer_max_s seconds.
  3. hold — zero delta-v for t_hold_s seconds. The ring is a bounded relative orbit with no secular drift, so holding costs no fuel; a hold at a fixed RTN point would have to be paid for every step.
  4. commit — the :class:~orbitalgym.policies.heuristic.glideslope.GlideslopeToLady command, with avoidance as configured, until the episode ends.

Phases are per-vehicle and live in policy_state; transitions are jnp.where selects on traced values, so the policy runs under jit and vmap. Phase time is counted in dt increments from the first call rather than read off the absolute t, so the schedule is measured from the start of the episode.

settle_tol_mps decides how well the hold holds. A residual velocity error dv violates the bounded-orbit condition and leaves an along-track drift of about 2 pi dv / (1.5 n) metres per orbit, so how tightly the transfer ends sets how far the vehicle walks off the ring during the very phase the schedule exists to create. Measured on a 2 km standoff ring approached from 3 km at a 0.4545 m/s per-step cap, over six start phases, settle_tol_mps=0.2 settles in 510 to 630 s and holds the amplitude within 47 to 75 percent of the standoff over an orbit. The default 0.02 settles in 750 to 840 s and holds within 3.4 to 7.7 percent, so the hold stays near the ring rather than depending on where in the ellipse the looser threshold happens to stop the transfer.

The transfer carries no avoidance term by design. Pushing away from a guard while gliding onto the ring would fight the ring target and leave the vehicle short of it when the phase ends. Avoidance acts in the commit phase, through the wrapped policy's avoidance_gain_mps and avoidance_sigma_m.

Every phase's command is capped at max_dv_mps by the norm clip the glideslope law applies. The agent view is a belief with mean (N_obs, N_total, d) or a flat observation of the same numbers, as :func:~orbitalgym.policies.heuristic.views.mean_from_view describes.

init_state

init_state() -> PhasedBanditState

Every vehicle starts coasting with its phase clock at zero.

JitteredPolicy

orbitalgym.policies.heuristic.jittered.JitteredPolicy dataclass

JitteredPolicy(
    base: Policy,
    sigma: float = 0.01,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Wraps a base policy and adds PRNG-keyed Gaussian jitter to its action.

The jitter is drawn from N(0, sigma²I) with the same shape as the base action. Setting sigma=0.0 is a pure passthrough.

base is required — instantiate with JitteredPolicy(base=..., sigma=...).

HeuristicWithFallbackPolicy

orbitalgym.policies.controlled.heuristic_with_fallback.HeuristicWithFallbackPolicy dataclass

HeuristicWithFallbackPolicy(
    primary: Policy,
    fallback: Policy,
    threshold: float = 0.5,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Confidence-gated routing wrapper.

policy_state semantics: a scalar confidence in [0, 1]. The wrapped primary's own state is not threaded through this class — primaries that need state should be wrapped with their own state-handling.

Only the dv field is gated by confidence. Non-dv Command fields (e.g. Communicate.active, Communicate.payload) propagate from the primary policy unchanged — the fallback's non-dv fields are discarded. If the fallback should drive non-dv fields too, replace this wrapper with a custom routing policy.

CompositeActionPolicy

orbitalgym.policies.controlled.composite_action.CompositeActionPolicy dataclass

CompositeActionPolicy(
    base: Policy,
    offset: Policy,
    n_vehicles: int = 0,
    command_cls: Any = None,
)

Sums actions from base and offset. Both must produce same-shape actions.

Only the dv field is summed. Non-dv Command fields (e.g. Communicate.active, Communicate.payload) propagate from the base policy unchanged — the offset policy's non-dv fields are discarded. This matches the typical "scripted base + learned residual" usage where the residual is a Δv correction only.