Skip to content

Pluggable simulator branch#97

Open
itokeiic wants to merge 27 commits into
ci-group:mainfrom
itokeiic:pluggable-simulator
Open

Pluggable simulator branch#97
itokeiic wants to merge 27 commits into
ci-group:mainfrom
itokeiic:pluggable-simulator

Conversation

@itokeiic

Copy link
Copy Markdown

Contains a tutorial of the backend simulator+RL swapping.

A-lamo and others added 27 commits May 14, 2026 14:59
Sketches the planned URDF backend (Isaac Lab UrdfConverter as the
URDF→USD half) alongside the existing blueprint_to_usd stub.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Schema refactor preparing for blueprint_to_urdf. Mass and inertia are now
read off the Blueprint nodes themselves rather than passed as backend
kwargs, so MJCF / URDF / future USD backends share one source of truth.

- blueprint.py: add CylindricalCrossSection / HollowTubeCrossSection /
  RectangularCrossSection sub-objects, each with `area` and
  `principal_inertia(length, mass)`. Extend ArmNode with `density` +
  `cross_section` + derived `mass` / `inertia_diag` properties. Extend
  MotorNode with derived `mass` / `radius` / `thickness` / `inertia_diag`
  via PROPELLER_LIBRARY lookup. Default cross-section is HollowTube
  (4mm OD / 3mm ID) which with density=1500 kg/m³ reproduces existing
  BEAM_DENSITY = 0.034 kg/m. Extend from_dict to rebuild cross_section
  sub-dataclasses on load.
- propeller_data.py: add motor_radius / motor_thickness per prop entry,
  values from the APC propeller-motor pairing table — outer-can radius
  plateaus at ~14mm for prop5+ (all use 22mm-class stators), only
  thickness grows.
- backends.py: blueprint_to_mjspec drops motor_mass / arm_mass /
  arm_radius / motor_radius / motor_thickness kwargs, reads from node
  properties, dispatches arm geometry on isinstance(cross_section, …)
  (capsule for circular, box for rectangular). blueprint_to_urdf stub
  signature trimmed to match.
- DRONE_BLUEPRINT_PLAN.md: Phase 3 marked in-progress, blueprint_to_mjspec
  description refreshed, new §6 Design decisions captures the 11 choices
  made on this branch (URDF-first vs direct USD, hybrid mass model,
  cross-section sub-object, compliant-joint two-layer pattern, etc.).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Walks the blueprint tree and emits a URDF file: one <link> per
CorePlate / Arm / Motor node (with diagonal <inertial> sourced from the
node's derived mass + inertia_diag) and one <joint type="fixed"> per
parent-child edge. Arm geometry dispatches on cross-section type:
<cylinder> for solid / hollow-tube, <box> for rectangular. Rotor visuals
are folded into the parent motor link (no separate rotor link; rotor
mass is already lumped into motor.mass). No actuators emitted — Isaac
Lab applies thrust at runtime via force on the motor link's local +Z.

This is the URDF half of the Blueprint → URDF → USD path; the soft_
airframe_optimization repo already has the URDF → USD half via
isaaclab.sim.converters.UrdfConverter, so the two combine into a full
Isaac Lab asset pipeline.

Compliant joints (a future ArmNode.CompliantJoint annotation) will
swap the joint type to "revolute" with zero physics stiffness and stash
the non-linear τ(θ) law as sidecar ariel:* attributes, mirroring soft_
airframe's morphy:* USD two-layer pattern.

Smoke test: decoder-built quad emits 9 links + 8 joints; mixed-cross-
section drone correctly dispatches box vs cylinder per arm; output is
well-formed XML that MuJoCo's URDF parser accepts without error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The conversion is split across two scripts because ariel requires
Python 3.12 (PEP 695 type-statement syntax) while the local isaaclab
conda env runs Python 3.11. The URDF file is the boundary between the
two envs.

* scripts/blueprint_to_urdf.py — runs in the ariel env. Builds a
  DroneBlueprint (either a preset or from a saved JSON), calls
  blueprint_to_urdf, writes the .urdf to --out.

* scripts/urdf_to_usd.py — runs in the isaaclab env. Takes a URDF
  path and writes a USD via isaaclab.sim.converters.UrdfConverter.
  All joints in the v1 URDF are fixed, so joint_drive is configured
  with target_type="none" and zero PD gains (just satisfies the
  required-field validator). Progress messages go to stderr to survive
  Isaac Sim's stdout capture.

End-to-end smoke test passed: blueprint_to_urdf produces a 4-arm quad
URDF; urdf_to_usd produces the standard Isaac Lab layered USD output
(quad.usd + configuration/{quad_base, quad_physics, quad_robot,
quad_sensor}.usd, ~10 KB total).

Sample run:
  python scripts/blueprint_to_urdf.py --preset quad --out /tmp/quad.urdf
  /home/keiichi-ito/miniconda3/envs/isaaclab/bin/python \
      scripts/urdf_to_usd.py --headless \
      --input /tmp/quad.urdf --output_dir /tmp/quad_usd
  # (the second invocation needs ${ISAAC_SIM}/setup_conda_env.sh sourced)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Builds an X-quad blueprint, calls blueprint_to_urdf, parses the result
back and reports link/joint counts, total mass, and which geometry tags
ended up in the URDF — verifying the arm cross-section dispatch
(<cylinder> for default HollowTube vs <box> for --rect-arms).

End-to-end smoke check: both the default and --rect-arms outputs round-
tripped cleanly through scripts/urdf_to_usd.py to USD (1453 bytes,
standard Isaac Lab layered layout).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Updated Phase 3 in §5 from "In progress" to "Largely landed for rigid
  drones," with a numbered list of the 5 commits that landed today and
  what each does. Calls out what's still pending: compliant joints
  (schema + URDF revolute + USD ariel:* attrs + example), the optional
  wrapper script, and pytest integration coverage.
* Added §6 entries 12–14 capturing decisions made during the pipeline
  bring-up: two-env split with URDF as the boundary, stderr-based
  logging in urdf_to_usd.py to survive Isaac Sim's stdout capture, and
  the joint_drive workaround for the all-fixed-joints case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces PEP 695 type-statement syntax (Python 3.12+) with PEP 613
TypeAlias, so ariel can run in the Python 3.11 conda env that Isaac
Sim 5.1 requires. Goal: one unified env in which ariel and Isaac Lab
both work, eliminating the two-script subprocess split documented in
DRONE_BLUEPRINT_PLAN.md §6 entry 12.

Changes:

* 31 `type X = ...` statements rewritten to `X: TypeAlias = ...`
  across 14 files in src/ariel/{ec,parameters,utils,body_phenotypes}
  and examples/{re_book,z_ec_course}. Each file adds `TypeAlias` to
  its existing typing import (or adds a new import where there was
  none).

* src/ariel/ec/ea.py: `class EAOperation[**P]:` (PEP 695 generic class
  syntax) → `class EAOperation(Generic[P]):` using the module-level
  `P = ParamSpec("P")` that already existed at line 26.

* src/ariel/ec/individual.py: dropped the self-referential typing of
  JSONType / JSONIterable. PEP 695 `type X = ...` evaluates the body
  lazily, so recursive aliases worked transparently; PEP 613 evaluates
  eagerly, and forward-ref strings then break at runtime when SQLModel
  introspects `JSONIterable | None` field annotations. Nested values
  fall through to `Any` — same approach pydantic.JsonValue takes for
  runtime use.

* pyproject.toml: `requires-python` from `>=3.12` to `>=3.11`.

Validation:

* Python 3.12 (current dev env): full import chain works, blueprint
  decoder + URDF + MJCF backends all functional. No regression.
* Python 3.11 (isaaclab conda env): syntax is now clean — what was
  `SyntaxError: invalid syntax (archive.py, line 14)` is now just
  `ModuleNotFoundError: No module named 'sqlmodel'` (an env-setup
  issue, not a code issue). The migration is complete; remaining
  work is installing ariel's runtime deps into the isaaclab env.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Phase 3 follow-on commit list extended with the 3.11 migration; the
  "Still pending" section drops the obsoleted wrapper-script item
  (single env makes it unnecessary) and adds the dep-pin item as a
  deferred stretch.
* §6 entry 12 (the two-env split rationale) marked superseded with a
  forward pointer to the new entry.
* §6 entry 15 added documenting the migration: scope, the
  self-referential-alias subtlety, and validation status (URDF→USD
  pipeline runs end-to-end in one isaaclab env despite the pip
  resolver's warnings about numpy 2 / gymnasium 1.3 / torch CUDA
  build swaps).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous version said the migration was for "simulator-agnostic"
without naming the use case that actually forced it: EA objective
functions that simulate each morphology, where the two-env subprocess
split would pay ~60–90 s of Isaac Sim launch overhead per individual.
Also lists alternatives we considered (Isaac→3.12, long-running
worker, MuJoCo-only EA loop) and why we picked the migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Defines the BlueprintGateEnv Protocol — ariel's contract for a
simulator-backend-agnostic gymnasium VecEnv constructed from a
DroneBlueprint. The PPO trainer (and any other consumer) accepts any
implementation; collaborators can plug in their own simulator while
keeping ariel's EA + learning infrastructure. This is the
architectural payoff of the unified-env work in §6 entry 15.

Phase 1 ships:

* src/ariel/simulation/tasks/blueprint_gate_env.py — the
  @runtime_checkable BlueprintGateEnv Protocol, plus
  NumpyBlueprintGateEnv: a thin subclass of the existing DroneGateEnv
  that adds a Blueprint → propellers-list adapter at the constructor.
  Inherits all VecEnv methods, gate definitions, reward shaping, and
  observation/action spaces unchanged.

* src/ariel/simulation/tasks/isaaclab_gate_env.py —
  IsaacLabBlueprintGateEnv stub with the planned constructor
  signature and NotImplementedError. Pins the API so the tutorial's
  --simulator isaaclab path compiles against the intended shape;
  body is Phase 2 work.

* tutorials/pluggable_simulator/{README.md, train.py} — README walks
  through the architecture, the Protocol contract, and a five-step
  "add your own backend" recipe. train.py is the unified entry point
  with --simulator {numpy,isaaclab} dispatch, demonstrating that the
  PPO loop is identical across backends.

Bug fix bundled in:

* src/ariel/simulation/tasks/drone_gate_env.py: renamed self.seed →
  self._seed. DroneGateEnv stored its seed as an int attribute
  shadowing the inherited VecEnv.seed() method that stable-baselines3
  PPO calls in set_random_seed. Anyone passing seed= to PPO would
  have hit "TypeError: 'int' object is not callable". Surfaced
  because the new tutorial passes a seed through the entire pipeline;
  fix is targeted (3 lines across __init__ and reset_seed).

Validation:

* Smoke test (ariel 3.12 venv): PPO 2000 timesteps, num_envs=4,
  quad preset → completes in 4.7 s at ~6600 env-steps/sec.
  ep_rew_mean ~-21 (expected for a from-scratch policy).
  Protocol isinstance assert passes.

* Smoke test (unified isaaclab 3.11 conda env): hits a segfault
  during PPO startup, likely numpy 2 ABI conflict with stable-
  baselines3 compiled deps. This is exactly the "if pip conflicts
  bite elsewhere" risk we flagged in §6 entry 15; promoted to a
  blocking item in the "Still pending" list.

Doc updates (DRONE_BLUEPRINT_PLAN.md):

* Phase 3 follow-on commit list extended with the pluggable-
  simulator branch and entry 7.
* "Still pending" rewritten: Phase 2 (Isaac Lab implementation) is
  the next item; numpy<2 pinning promoted from stretch since the
  segfault means it now blocks RL training in the unified env.
* §6 entry 17 added documenting the design decisions: why the seam
  is a Protocol (not ABC, not gymnasium-only duck typing), why
  NumpyBlueprintGateEnv is a subclass rather than composition,
  and the seed-attribute bug we found and fixed in passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lands the Isaac-Lab-side env construction for the pluggable-simulator
architecture. The full Blueprint → URDF → USD → DirectRLEnv → N parallel
drones pipeline runs in-process. Trains via random-action stepping for
v1; rl_games PPO wiring is in place but blocked by a layered env-stack
issue (see §6 entry 17). Architecture pivot from Phase 1: the Isaac Lab
backend follows the DirectRLEnv shape rather than the gymnasium VecEnv
Protocol, accepting that different simulator ecosystems use different
RL libraries.

Renamed (preserves git history):
* src/ariel/simulation/tasks/isaaclab_gate_env.py
  → src/ariel/simulation/tasks/isaaclab_hover_env.py

Implementation (isaaclab_hover_env.py):
* `make_blueprint_usd(blueprint, output_dir)` — in-process URDF→USD
  conversion via blueprint_to_urdf + UrdfConverter. Requires Isaac Sim
  app to already be running.
* `IsaacLabBlueprintHoverEnvCfg` (@configclass extending DirectRLEnvCfg)
  with a `.from_blueprint(bp, num_envs=...)` classmethod that generates
  the USD as a side effect and slots its path into `robot.spawn.usd_path`.
  Overrides default `joint_pos`/`joint_vel` `{".*": 0.0}` regexes to
  empty dicts, since our rigid-drone articulations have zero movable
  joints (otherwise: "Not all regular expressions are matched!").
* `IsaacLabBlueprintHoverEnv(DirectRLEnv)` — modeled on Isaac Lab's
  reference QuadcopterEnv. 4-action thrust+moment wrench at root body;
  12-D observation (root_lin_vel_b, root_ang_vel_b, projected_gravity_b,
  desired_pos_b); reward = -distance_to_goal × step_dt (numerically
  stable for all distance values, per the project lead's design note);
  done on episode length or z bounds.
* `make_rl_games_agent_cfg(...)` — returns an rl_games PPO config dict
  matched to Isaac Lab's reference quadcopter `rl_games_ppo_cfg.yaml`.
  Defined as a function (not @configclass) because rl_games is dict-
  configured. Ready to plug into Phase 2.5 once the env-stack issue
  is resolved.

train.py:
* New `main_isaaclab` that launches AppLauncher, generates USD,
  constructs env, then runs a random-action stepping loop for
  `max_iterations × 24` steps (the rl_games horizon shape). Verifies
  end-to-end correctness of obs/reward/done plumbing.
* Per-step progress + final stats logged to stderr (stdout is captured
  by Isaac Sim's launcher; same trick we used in urdf_to_usd.py).
* Peek-parser dispatch on `--simulator` so the numpy path doesn't
  pay the Isaac Lab import cost.

Verified:
* `--simulator numpy --total-timesteps 2000` in the ariel 3.12 venv:
  PPO trains in 3.8 s at ~525 steps/sec.
* `--simulator isaaclab --headless --num-envs 16 --max-iterations 3`
  in the unified isaaclab conda env: env constructs + spawns 16
  drones + steps 72 times in 1.6 s. Mean reward ~ -0.03 (consistent
  with -distance × dt for small distances and short episodes).

Env-stack issues encountered (documented in §6 entry 17's "Phase 2.5
deferred" subsection):
* sb3 + numpy 2 ABI segfault → avoided by NOT routing Isaac Lab via
  sb3 / `isaaclab_rl.sb3` adapter.
* `isaaclab_rl.rsl_rl` adapter vs `rsl-rl-lib`: tried 3.0.1 and 5.3.0;
  both fail on different config-shape mismatches (`optimizer`,
  `share_cnn_encoders`, `class_name`). Shim
  `handle_deprecated_rsl_rl_cfg` didn't catch the relevant fields.
* `rl_games` + Isaac Sim bundled torch: tensorboard import pulls in
  bundled pip_prebundle torch which transitively imports older
  tensorflow → jax → tries `numpy.dtypes.StringDType` on a bundled
  older numpy. v1 ships with a random-action stepping loop instead;
  PPO wiring is reachable in `make_rl_games_agent_cfg` and ready to
  plug in once the env-stack is resolved (Phase 2.5).

Doc updates:
* tutorials/pluggable_simulator/README.md: rewrote the architecture
  section as a 2-row table (numpy / isaaclab) with honest Phase 2
  status. Updated the "Adding your own simulator" recipe to cover
  both contracts (gymnasium VecEnv path AND Isaac Lab DirectRLEnv
  path).
* DRONE_BLUEPRINT_PLAN.md: added Phase 3 follow-on commit 8; rewrote
  §6 entry 17 to capture the architectural pivot (one Protocol →
  two contracts) and the layered env-stack issues that informed it.
  Promoted Phase 2.5 to the "Still pending" headline item.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stops ariel's base package from owning Isaac-Lab-sensitive binary
deps (numpy 2, torch, gymnasium), and unblocks two import chains
that were dragging optional heavy deps into common code paths. This
is the architectural prerequisite to actually running rl_games PPO
on the Isaac Lab backend — the Phase 2.5 plan documented in §6
entry 15 / Phase 3 "Still pending" of DRONE_BLUEPRINT_PLAN.md.

pyproject.toml — dependency ownership split:
* `[project.dependencies]` trimmed to simulator-agnostic essentials.
  Base `numpy` is now pinned conservatively to `>=1.26,<2` (was
  `>=2.0.2`) so installing ariel into an Isaac Lab env doesn't drag
  in numpy 2's ABI and segfault sb3.
* New optional extras: `rl-sb3` (gymnasium==1.2.1 + sb3>=2.7.0),
  `torch` (torch + torchvision), `viz` (opencv/pillow/panel/plotly/
  kaleido), `fabrication` (cadquery), `experimental` (the long tail
  of attrs/deap/dm-control/ipykernel/jupyter/keyboard/manifold3d/
  nevergrad/nicegui/omegaconf/polars/pyserial/pyvista). torch and
  gymnasium leave [project.dependencies] entirely — Isaac Lab's env
  owns those.

README.md — installation matrix:
* Documents `uv sync --extra rl-sb3 --extra torch` for the sb3 path,
  and crucially `pip install -e . --no-deps` for installing ariel
  into an Isaac Lab env without replacing simulator-owned binaries.

src/ariel/ec/drone/genome_handlers/__init__.py — lazy imports:
* Replaced eager handler imports with `__getattr__`-based lazy
  resolution. Importing `genome_handlers` no longer pulls in `fcl`
  (or any other optional heavy dep) just because something under
  the package is touched. Verified: `from ariel.ec.drone.
  genome_handlers import GenomeHandler` succeeds with fcl absent
  from sys.modules.

src/ariel/body_phenotypes/drone/phenotype_assembly/parts/ — new
fabrication package (added by user):
* 4 CAD-part factories: `create_core_plate`, `create_arm_mount`,
  `create_motor_arm`, `create_motor_mount`. Imported by the
  assembly code; previously missing, so `fabrication` extra
  imports failed even with cadquery installed.

.gitignore — source-tree `parts/` exception:
* The pre-existing `parts/` rule at line 33 is the standard
  Python/buildout block; it was silently swallowing the new
  source-tree `parts/` package above. Added
  `!src/**/parts/` exception so source `parts/` packages are
  tracked normally; the buildout-style ignore still applies to
  output directories elsewhere.

DRONE_BLUEPRINT_PLAN.md — extensive doc updates:
* Phase 2.5 in "Still pending" rewritten with a concrete Option A
  execution recipe (clean conda env from Isaac Lab's
  environment.yml + `pip install -e . --no-deps` for ariel) and an
  acceptance checklist. Options B and C demoted to
  diagnostic-only fallbacks with their own minimal runbooks.
* The old "Targeted dep pins" pending item replaced with a summary
  of what was implemented this session.
* §6 entry 15 (the unified-env entry) expanded with the
  dependency-ownership split rationale.
* New §6 entry 18 documenting the lazy-imports + parts/ fixes and
  the clean-env smoke validation across `rl-sb3`, `torch`, `viz`,
  `fabrication`, `experimental` extras.

uv.lock — regenerated to match pyproject.toml's new dependency
shape.

Co-Authored-By: Keiichi Ito (Phase 2.5 architectural changes)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes two reproducibility gaps that Copilot's readiness review
flagged on the otherwise-complete Phase 2.5 work:

1. **Vendor Isaac Lab's `environment.yml`** to
   `tutorials/pluggable_simulator/isaaclab-env.yml`, snapshotted at
   IsaacLab SHA `f4aa17f87e2`. The Option A recipe now references
   this vendored copy instead of `$ISAACLAB_ROOT/environment.yml`,
   so the recipe is reproducible across upstream Isaac Lab
   evolution. The file header documents the upstream SHA and
   reminds future maintainers to refresh deliberately, not silently
   follow upstream.

2. **Binary-guardrail check** added to the Option A recipe. Steps 4
   and 6 of the recipe now snapshot the installed versions of
   `torch / torchvision / gymnasium / numpy` immediately before and
   after `pip install -e . --no-deps`, and diff them. The recipe
   exits non-zero with a clear error if ariel's install bumped any
   simulator-owned binary. Catches the failure mode that motivated
   the whole dependency-ownership split (numpy 2 / gymnasium 1.3
   sneaking in via transitive resolution).

The Option A acceptance checklist gained two new boxes for the
pre-install snapshot and the post-install diff being empty
(`BINARIES UNCHANGED ✓`).

Phase 2.5 trial readiness moves from ~80% to closer to 100% on the
doc/spec axis; the remaining work is purely the execution pass
(creating the conda env, running the install, ticking the
checklist) which needs a human session, not more docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Makes the tutorial self-contained — readers don't have to dig into
DRONE_BLUEPRINT_PLAN.md to find the conda env build. New §3 covers
both backends:

* §3a NumPy backend: one-line `uv sync --extra rl-sb3 --extra torch`
  for the ariel venv.
* §3b Isaac Lab backend: full copy-paste recipe (vendored
  isaaclab-env.yml + `./isaaclab.sh -i` + ariel `--no-deps` install
  + binary guardrail diff). Mirrors the Option A recipe in
  DRONE_BLUEPRINT_PLAN.md §5; the design doc keeps the deeper "why"
  (env-stack issues, alternatives considered), the tutorial just
  shows you how to build the env.

Renumbered downstream sections so the structure stays contiguous:
§3 → §4 Running the shipped backends, §4 → §5 Adding your own
simulator, §5 → §6 Why this matters.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes Phase 2.5 with all seven Option A acceptance boxes ticked
except the last one (real rl_games PPO training), which is now
unblocked. Executed the recipe end-to-end on 2026-05-27 — 72
env-steps through Isaac Sim PhysX in 0.4 s on 16 parallel envs,
binaries unchanged.

Code: lazy __init__.py refactor (4 files):

Eager package-level imports were the failure mode that kept tripping
us during the execution pass. Each retry surfaced a different missing
dep — sympy, sqlalchemy, sqlmodel, pydantic_settings — none of which
the Isaac Lab path actually needed, but all of which were dragged in
by transitive __init__.py chains.

* src/ariel/simulation/__init__.py: was eagerly importing
  ariel.simulation.mujoco_worker (needs mujoco). Now lazy.
* src/ariel/ec/__init__.py: was eagerly importing Archive / EA /
  Individual / Population (needs sqlalchemy + sqlmodel +
  pydantic-settings). Now lazy via a single _LAZY_IMPORTS dispatch
  table.
* src/ariel/body_phenotypes/drone/__init__.py: was eagerly importing
  operations (which needs ariel.ec.ea → pydantic-settings). Now lazy.
* src/ariel/simulation/drone/__init__.py: was eagerly importing
  DroneSimulator (needs sympy). Now lazy.

External API unchanged — `from ariel.ec import Archive` etc. still
work; the import is just deferred until the symbol is accessed.

Recipe fixes (DRONE_BLUEPRINT_PLAN.md §5 + tutorials/pluggable_simulator/README.md §3b):

* Insert `source $ISAACLAB_ROOT/_isaac_sim/setup_conda_env.sh` as
  step 3b — missing prerequisite for bare `python` invocations to
  find isaacsim / omni.* / pxr on PYTHONPATH. `./isaaclab.sh -p`
  handled this internally so step 3 worked; bare `python` from step
  7 onward did not.
* Add step 6b: install pure-Python ariel deps that --no-deps
  skipped (networkx, rich, pydantic, pydantic-settings, sqlalchemy,
  sqlmodel, numpy-quaternion, matplotlib, mujoco), with
  `--constraint /tmp/ariel_phase25_binaries_before.txt` so pip can't
  bump the simulator-owned binaries. Skip evotorch + mujoco-mjx
  (they fight Isaac Lab's torch / jax / numpy).
* Replace step 7's DroneGateEnv import smoke with a Blueprint-chain
  smoke (blueprint, decoders, backends). DroneGateEnv was the NumPy
  backend's env; importing it dragged in EA orchestration deps that
  the Isaac Lab path intentionally doesn't pull in. The
  Blueprint-chain test exercises exactly what step 8 needs.

Acceptance checklist: marked the six trivially-testable boxes as
done with timestamps and observed values; the seventh (rl_games PPO
smoke) is the next session's task and explicitly flagged STILL
PENDING.

New §6 entries:

* Entry 19: Lazy __init__.py is required across ariel's package
  surface. Documents the pattern (TYPE_CHECKING re-exports +
  _LAZY_IMPORTS dispatch + __getattr__), the rule (any package
  re-exporting from submodules should be lazy), and the future-
  maintenance recommendation (new __init__.py files start lazy,
  not refactor later).

* Entry 20: Operational gotcha — failed Isaac Sim smoke runs leak
  processes. Documents the symptom (~120% CPU for hours after a
  3-iteration smoke "ended"), the cause (Isaac Sim's app threads
  don't co-operatively shut down on launcher errors), the
  detection command (`ps -u $USER | grep train.py`), and the
  cleanup command (`pkill -KILL -f "tutorials/pluggable_simulator/
  train.py"`). Observed five such orphans across Phases 2 and 2.5,
  cumulatively ~25 core-hours burned silently.

Tutorial README also gets a §3c "Operational gotcha: stale Isaac
Sim processes after a failed run" subsection mirroring entry 20,
so users encountering the issue have the fix at hand without
needing to dig into the design doc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Last Option A acceptance box now ticks empirically. Full Blueprint
→ URDF → USD → IsaacLabBlueprintHoverEnv → rl_games PPO trains
end-to-end in the ariel-isaaclab-train conda env. 3 PPO epochs in
0.9 s on 16 envs, fps_total 719 → 2787 → 3318 across epochs.

train.py — `--mode {train, step}` flag:

* `--mode train` (default): runs the full rl_games training path
  via Isaac Lab's `isaaclab_rl.rl_games` adapter and
  `rl_games.torch_runner.Runner`. The agent config comes from
  `make_rl_games_agent_cfg` in isaaclab_hover_env.py (mirrors Isaac
  Lab's reference QuadcopterPPO config).
* `--mode step`: the previously-default random-action env-stepping
  loop. Kept as an explicit alternative because it's faster and
  useful for env-construction sanity / debugging the Isaac-Lab-side
  env-stack without paying for a PPO run.

Restructured `main_isaaclab` into:

* `_isaaclab_step_smoke(env, args)`: random-action loop (unchanged
  from prior commit, just extracted).
* `_isaaclab_rl_games_train(env, args)`: real PPO, mirrors Isaac
  Lab's `scripts/reinforcement_learning/rl_games/train.py`. Wraps
  env via `RlGamesVecEnvWrapper`, registers in rl_games' global
  registries, builds `Runner(IsaacAlgoObserver())`, loads + resets +
  runs `{"train": True, ...}`.

Tutorial README §4 (Isaac Lab subsection) rewritten to describe both
modes with explicit invocations; `train.py` example block in the
header docstring updated similarly.

DRONE_BLUEPRINT_PLAN.md updates:

* Phase 3 status header: "Largely landed" → "✅ Landed end-to-end
  for rigid drones." Notes today's execution.
* Phase 2.5 acceptance checklist: last box (rl_games PPO smoke)
  ticked with observed values (epoch fps, final reward, checkpoint
  path).

.gitignore: added `runs/` (rl_games auto-creates ./runs/<exp>/nn/
when training launches from the repo root). Sits alongside the
existing source-tree `parts/` exception.

Phase 2.5 deliverables (full list):
- Dependency-ownership split in pyproject.toml (commit 53d5fb0)
- Vendored isaaclab-env.yml + binary guardrail (commit 7dfaea4)
- Tutorial env-setup section in README (commit 2613bef)
- Lazy __init__.py refactor + recipe corrections (commit 4b68081)
- Re-enabled rl_games PPO training (this commit)

The architecture validated end-to-end: pluggable-simulator
(commit 57592d4) + Phase 2 Isaac Lab env (commit 47a1af7) + Phase
2.5 dep / env work runs from one clean conda env via one
`train.py --simulator isaaclab` command.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- README §6: new stub for partners wiring their RL pipeline to ariel's
  EA layer (three roles, five-step recipe, forward link to companion).
- connect_your_pipeline.md: companion doc with file-by-role table,
  in-process vs subprocess patterns, fitness-extraction options,
  common pitfalls, and measured calibration numbers (~160s for 3 gen
  x 4 ind x 30 PPO epochs).
- evolve.py: subprocess-per-individual EA reference loop. Each
  individual gets a fresh Isaac Sim process; fitness comes from the
  rl_games checkpoint filename.
- train.py: --blueprint-json + --experiment-prefix flags so the EA
  driver can hand in per-individual morphologies. Hard-exit via
  os._exit() after PPO to bypass simulation_app.close()'s
  ~120% CPU hang (orphan-thread pattern already noted in README §3c).
- README cleanup: drop DRONE_BLUEPRINT_PLAN.md cross-refs and Phase
  2/2.5 jargon that didn't mean anything to consortium partners;
  status paragraph rewritten to current reality.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Role 2 sketch in connect_your_pipeline.md called
make_blueprint_usd with a positional output_dir, but the real
signature is keyword-only (def make_blueprint_usd(blueprint, *,
output_dir, robot_name="drone")). Copy-pasting the sketch would
have raised TypeError.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 28, 2026 06:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants