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
¶
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
¶
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
¶
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.
coast— zero delta-v fort_coast_sseconds.transfer— glide onto the natural 2:1 ellipse of radial amplitudestandoff_mat the vehicle's current phase angle (see :func:_ring_target), using :func:~orbitalgym.policies.heuristic.glideslope.glideslope_dvwith the commit policy'sslope_sandbrake_fractionand an arrival speed of zero, so the vehicle arrives on the ring state rather than through it. Ends when the state is withinsettle_tol_min position andsettle_tol_mpsin velocity of that target, or aftert_transfer_max_sseconds.hold— zero delta-v fort_hold_sseconds. 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.commit— the :class:~orbitalgym.policies.heuristic.glideslope.GlideslopeToLadycommand, 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 ¶
Every vehicle starts coasting with its phase clock at zero.
JitteredPolicy¶
orbitalgym.policies.heuristic.jittered.JitteredPolicy
dataclass
¶
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.