-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[Newton] Delegate homogeneous world prefix generation to ModelBuilder.replicate()
#7453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
a3884c5
5b12d9b
cbb17ab
e989fdd
844f4c4
cb0cbe6
f2ecc11
ef6ae37
be85711
0debe7b
378c376
26bb213
4daa8d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| Added | ||
| ^^^^^ | ||
|
|
||
| * Added :attr:`~isaaclab.cloner.ClonePlan.env_template`, the destination template for one | ||
| environment. Every row's destination is that template followed by the asset's path below the | ||
| environment, so it names the part a clone varies while the remainder is shared. It was | ||
| previously a constructor argument that the plan discarded, leaving a consumer holding a row | ||
| unable to recover it -- a destination carries no mark of where the environment ends. Backend | ||
| replication contexts receive it alongside ``global_paths``. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| Fixed | ||
| ^^^^^ | ||
|
|
||
| * Fixed a bodyless per-environment site carrying the label ``ft_0`` in every environment. Such a | ||
| site is now registered with the destination template of the clone-plan row that requested it and | ||
| labelled from the environment it lands in, so it reads e.g. ``/World/envs/env_3/ft_0``. Sites are | ||
| still resolved by index, so consumers that look them up by label and index are unaffected. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| Changed | ||
| ^^^^^^^ | ||
|
|
||
| * Changed the homogeneous Newton cloning path to let replication name each cloned entity for | ||
| the environment it lands in, instead of rewriting every replicated label afterwards. The | ||
| prototype's labels are rebased once -- a few hundred entries -- and | ||
| :meth:`~newton.ModelBuilder.replicate` is given the per-env roots, replacing a pass over | ||
| every label in every world that cost 215 ms on ``Isaac-Velocity-Flat-G1`` at 4096 | ||
| environments. The labels are identical either way; a prototype whose labels a per-world | ||
| prefix cannot spell keeps the previous path. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| from pxr import Usd, UsdGeom, UsdPhysics | ||
|
|
||
| from isaaclab.cloner import path as clone_path | ||
| from isaaclab.cloner.cloner_cfg import DEFAULT_ENV_TEMPLATE | ||
| from isaaclab.sim.utils.newton_model_utils import replace_newton_builder_shape_colors | ||
|
|
||
| from isaaclab_newton.renderers.visual_material import import_builder_visual_material_paths | ||
|
|
@@ -186,6 +187,37 @@ def _invert_xform(xform: Sequence[float] | np.ndarray) -> np.ndarray: | |
| return np.concatenate([-_quat_rotate(quat_inv, xform[:3]), quat_inv]) | ||
|
|
||
|
|
||
| def _site_label(env_root: str | None, label: str) -> str: | ||
| """Site label, beneath *env_root* when it has one and bare otherwise.""" | ||
| return f"{env_root}/{label}" if env_root else label | ||
|
|
||
|
|
||
| def _rebase_to_env(builder: ModelBuilder, env_root: str) -> bool: | ||
| """Rewrite every entity label relative to its environment root, in place. | ||
|
|
||
| Replication makes N copies that differ only in the environment they sit in, so a label is | ||
| ``<env><within-env>`` and only the first part varies. Returns whether every label could be | ||
| written that way: one outside the environment, or one naming the environment root itself, | ||
| has no within-environment part a per-world prefix could carry. | ||
| """ | ||
| rebased = [] | ||
| for labels in ( | ||
| builder.body_label, | ||
| builder.joint_label, | ||
| builder.shape_label, | ||
| builder.articulation_label, | ||
| builder.constraint_mimic_label, | ||
| ): | ||
| for index, label in enumerate(labels): | ||
| suffix = clone_path.relative_to(label, env_root) if isinstance(label, str) else None | ||
| if not suffix: | ||
| return False | ||
| rebased.append((labels, index, suffix.lstrip("/"))) | ||
| for labels, index, suffix in rebased: | ||
| labels[index] = suffix | ||
| return True | ||
|
|
||
|
|
||
| def replicate_builder_mapping( | ||
| builder: ModelBuilder, | ||
| sources: Sequence[str], | ||
|
|
@@ -195,12 +227,26 @@ def replicate_builder_mapping( | |
| source_builders: dict[str, ModelBuilder], | ||
| *, | ||
| source_site_indices: dict[int, dict[str, list[int]]] | None = None, | ||
| env_root_sites: dict[str, wp.transform] | None = None, | ||
| env_root_sites: dict[str, tuple[wp.transform, str | None]] | None = None, | ||
| env_ids: torch.Tensor | None = None, | ||
| env_template: str = DEFAULT_ENV_TEMPLATE, | ||
| per_world_builder_hooks: Sequence[Callable[[ModelBuilder, int, list[float], list[float]], None]] = (), | ||
| ) -> tuple[dict[str, list[list[int]]], list[wp.transform]]: | ||
| """Replicate source builders into per-env Newton worlds.""" | ||
| ) -> tuple[dict[str, list[list[int]]], list[wp.transform], bool]: | ||
| """Replicate source builders into per-env Newton worlds. | ||
|
|
||
| Returns the per-env site indices, the per-env world transforms, and whether replication | ||
| already named each entity for the env it landed in. | ||
|
|
||
| Args: | ||
| env_root_sites: Site transform and the destination template naming its env, per label. | ||
| env_ids: Environment ids for the destination worlds. Given with an environment that | ||
| owns the prototype, replication names each copy and the caller does not have to | ||
| rewrite the entity labels afterwards. | ||
| env_template: Destination template for one environment, from the clone plan. | ||
| """ | ||
| source_site_indices = source_site_indices or {} | ||
| env_root_sites = env_root_sites or {} | ||
| env_ids_list = env_ids.tolist() if env_ids is not None else None | ||
| num_worlds = mapping.size(1) | ||
| local_site_map: dict[str, list[list[int]]] = {} | ||
| positions_np = positions.detach().cpu().numpy().astype(np.float32, copy=False) | ||
|
|
@@ -223,8 +269,12 @@ def replicate_builder_mapping( | |
| # by world_xforms[0] so R_w = world_xform_w * inv(world_xform_0) lands each | ||
| # copy at world_xform_w * xform. | ||
| site_local_indices: dict[str, list[int]] = {} | ||
| for label, xform in env_root_sites.items(): | ||
| idx = source_builder.add_site(body=-1, xform=wp.transform_multiply(world_xforms[0], xform), label=label) | ||
| for label, (xform, destination_template) in env_root_sites.items(): | ||
| # Every copy shares one label; the env name is applied after, by ``label_prefixes`` | ||
| # below or by ``rename_builder_labels``. ``can_batch`` makes either one exact. | ||
| root = sources[0] if destination_template else None | ||
| site_xform = wp.transform_multiply(world_xforms[0], xform) | ||
| idx = source_builder.add_site(body=-1, xform=site_xform, label=_site_label(root, label)) | ||
| site_local_indices.setdefault(label, []).append(idx) | ||
| for label, indices in source_site_indices.get(id(source_builder), {}).items(): | ||
| site_local_indices.setdefault(label, []).extend(indices) | ||
|
|
@@ -234,21 +284,30 @@ def replicate_builder_mapping( | |
| stride = source_builder.shape_count | ||
| source_xform_inv = _invert_xform(xforms_np[0]) | ||
| xforms = _compose_world_xforms(positions_np, quaternions_np, source_xform_inv) | ||
| builder.replicate(source_builder, num_worlds, xforms=xforms) | ||
|
|
||
| # One source populating every world is the shape replication can name itself: rebase the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The wording here is a bit confusing "every world is the shape replication can name itself:" |
||
| # prototype's labels once -- a few hundred entries -- and let each copy carry its own | ||
| # env root, instead of rewriting every label in every world afterwards. | ||
| label_prefixes = None | ||
| prototype_env = clone_path.match(sources[0], env_template) if env_ids is not None else None | ||
| if prototype_env is not None and _rebase_to_env(source_builder, env_template.format(prototype_env.instance)): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Warning · Implementation — Rebase mutates retained prototype builders
|
||
| label_prefixes = [env_template.format(env_id) for env_id in env_ids.tolist()] | ||
| builder.replicate(source_builder, num_worlds, xforms=xforms, label_prefixes=label_prefixes) | ||
|
|
||
| for label, local_indices in site_local_indices.items(): | ||
| local_site_map[label] = [ | ||
| [base_shape + world * stride + local for local in local_indices] for world in range(num_worlds) | ||
| ] | ||
|
|
||
| return local_site_map, world_xforms | ||
| return local_site_map, world_xforms, label_prefixes is not None | ||
|
|
||
| source_world_indices = mapping.to(dtype=torch.int64).argmax(dim=1).tolist() | ||
|
|
||
| # Per-world placements for every env-root site, composed up front so the per-world loop | ||
| # below only indexes rows. | ||
| root_site_xforms = { | ||
| label: _compose_world_xforms(positions_np, quaternions_np, xform) for label, xform in env_root_sites.items() | ||
| label: (_compose_world_xforms(positions_np, quaternions_np, xform), destination_template) | ||
| for label, (xform, destination_template) in env_root_sites.items() | ||
| } | ||
| # Same for the source placements, but only for the occupied ``(row, col)`` pairs of the | ||
| # mapping: composing a dense ``num_rows x num_worlds`` table would blow up on heterogeneous | ||
|
|
@@ -274,8 +333,11 @@ def replicate_builder_mapping( | |
| for col in range(num_worlds): | ||
| builder.begin_world() | ||
|
|
||
| for label, world_site_xforms in root_site_xforms.items(): | ||
| site_idx = builder.add_site(body=-1, xform=world_site_xforms[col], label=label) | ||
| for label, (world_site_xforms, destination_template) in root_site_xforms.items(): | ||
| # Named here, not by ``rename_builder_labels``: that only rewrites labels in the | ||
| # worlds the requesting row covers, and an env-root site sits in every world. | ||
| env_root = destination_template.format(env_ids_list[col]) if destination_template and env_ids_list else None | ||
| site_idx = builder.add_site(body=-1, xform=world_site_xforms[col], label=_site_label(env_root, label)) | ||
| local_site_map.setdefault(label, [[] for _ in range(num_worlds)])[col].append(site_idx) | ||
|
|
||
| for row in rows_per_world[col]: | ||
|
|
@@ -291,7 +353,7 @@ def replicate_builder_mapping( | |
| hook(builder, col, xform_rows[col][:3], xform_rows[col][3:]) | ||
| builder.end_world() | ||
|
|
||
| return local_site_map, world_xforms | ||
| return local_site_map, world_xforms, False | ||
|
|
||
|
|
||
| _BUILTIN_LABEL_TYPES: tuple[str, ...] = ( | ||
|
|
@@ -310,8 +372,17 @@ def rename_builder_labels( | |
| destinations: Sequence[str], | ||
| env_ids: torch.Tensor, | ||
| mapping: torch.Tensor, | ||
| *, | ||
| skip_entity_labels: bool = False, | ||
| ) -> list[tuple[str, int]]: | ||
| """Rewrite source-root labels to per-env destination roots and return Fabric body bindings.""" | ||
| """Rewrite source-root labels to per-env destination roots and return Fabric body bindings. | ||
|
|
||
| Args: | ||
| skip_entity_labels: Whether the entity labels already name the env they are in, as they | ||
| do when replication was given the per-env prefixes. Only the string custom | ||
| attributes are rewritten then, since Newton cannot tell which of those name | ||
| entities. | ||
| """ | ||
| fabric_body_bindings: list[tuple[str, int]] = [] | ||
| bound_body_indices: set[int] = set() | ||
| env_ids_list = env_ids.tolist() | ||
|
|
@@ -346,14 +417,15 @@ def _rename_pair(values, worlds, src_root=source_root, roots=world_roots, *, col | |
| fabric_body_bindings.append((renamed_value, index)) | ||
| bound_body_indices.add(index) | ||
|
|
||
| for labels, worlds, collect_body_bindings in ( | ||
| (builder.body_label, builder.body_world, True), | ||
| (builder.joint_label, builder.joint_world, False), | ||
| (builder.shape_label, builder.shape_world, False), | ||
| (builder.articulation_label, builder.articulation_world, False), | ||
| (builder.constraint_mimic_label, builder.constraint_mimic_world, False), | ||
| ): | ||
| _rename_pair(labels, worlds, collect_body_bindings=collect_body_bindings) | ||
| if not skip_entity_labels: | ||
| for labels, worlds, collect_body_bindings in ( | ||
| (builder.body_label, builder.body_world, True), | ||
| (builder.joint_label, builder.joint_world, False), | ||
| (builder.shape_label, builder.shape_world, False), | ||
| (builder.articulation_label, builder.articulation_world, False), | ||
| (builder.constraint_mimic_label, builder.constraint_mimic_world, False), | ||
| ): | ||
| _rename_pair(labels, worlds, collect_body_bindings=collect_body_bindings) | ||
|
|
||
| custom_attrs = builder.custom_attributes.values() | ||
| worlds_by_freq = {attr.frequency: attr.values for attr in custom_attrs if attr.references == "world"} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use absolute imports here and elsewhere (feel free to change other imports in the file to match)