Add radial bounds to PositionLimits#940
Conversation
Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
🤖 Isaac Lab-Arena Review BotSummaryAdds optional world-XY disk/annulus bounds to the generic Findings🔵 Improvement: Test CoverageStrong. Constructor validation (incl. the invalid-radial parametrized cases), batching, weight scaling, annulus / radius-min-only losses, the at-center gradient, axis+radial composition, generic YAML attachment, and two solver integration tests are all covered. These are pure-compute (no VerdictShip it |
Greptile SummaryThis PR extends
Confidence Score: 4/5Safe to merge; the radial path is off by default and leaves existing rectangular-only relations completely unaffected. The gradient-steering helper is non-trivial but is validated by targeted gradient tests and two end-to-end solver tests. Two small gaps exist: center_x/center_y supplied without radial bounds are silently ignored rather than rejected, and the center_radius minus epsilon formula has a theoretical path to a negative intermediate value for non-float32 dtypes. Neither represents a present data-loss or correctness regression. relation_loss_strategies.py contains the most complex new logic (_radius_with_lower_bound_escape_direction) and would benefit from a second read; relations.py has the silent no-op for center coordinates without radial bounds. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["compute_loss"] --> B{radius bound set?}
B -- No --> C[Axis-only loss]
B -- Yes --> D["Compute radial_delta and radius"]
D --> E{Both bounds?}
E -- Yes --> F["_radius_with_lower_bound_escape_direction"]
E -- No --> G{radius_min only?}
G -- Yes --> F
G -- No --> H["single_boundary_linear_loss(radius, radius_max)"]
F --> I{escape_components non-zero?}
I -- Yes --> J["feasibility_steered_radius"]
J --> K["torch.where(inside_lower_bound, steered, radius)"]
I -- No --> L{at_center?}
L -- Yes --> M["center_radius with +x gradient"]
L -- No --> N["radius standard gradient"]
M --> O["torch.where(at_center, center_radius, radius)"]
N --> O
K --> P["band or boundary loss"]
O --> P
H --> P
C --> Q["total_loss x weight"]
P --> Q
Reviews (1): Last reviewed commit: "Fix combined annulus and axial descent" | Re-trigger Greptile |
alexmillane
left a comment
There was a problem hiding this comment.
Couple of comments.
Both of them relate to the combined/separate treatment of box and cylindrical constraints.
Clearly some thinking has gone into how these two interact. I'm wondering if the complexity added by handling this due to realistic issues we're likely to face.
| @@ -493,15 +493,24 @@ def __init__( | |||
| z_min: float | None = None, | |||
| z_max: float | None = None, | |||
| relation_loss_weight: float = 1.0, | |||
| center_x: float | None = None, | |||
| center_y: float | None = None, | |||
| radius_min: float | None = None, | |||
| radius_max: float | None = None, | |||
| ): | |||
There was a problem hiding this comment.
What do you think about separating this into two classes:
PositionLimitsBoxPositionLimitsCylindrical
That way the meaning of these (many) constructor arguments is separated and clear for the user. Right now they're mixed up and a bit difficult to determine what is used where.
| if relation.radius_min is not None and relation.radius_max is not None: | ||
| total_loss = total_loss + linear_band_loss( | ||
| self._radius_with_lower_bound_escape_direction(relation, radial_delta, radius), | ||
| relation.radius_min, | ||
| relation.radius_max, | ||
| slope=self.slope, | ||
| ) | ||
| elif relation.radius_min is not None: | ||
| total_loss = total_loss + single_boundary_linear_loss( | ||
| self._radius_with_lower_bound_escape_direction(relation, radial_delta, radius), | ||
| relation.radius_min, | ||
| slope=self.slope, | ||
| penalty_side="less", | ||
| ) | ||
| else: | ||
| total_loss = total_loss + single_boundary_linear_loss( | ||
| radius, relation.radius_max, slope=self.slope, penalty_side="greater" | ||
| ) |
There was a problem hiding this comment.
Can we get a comment on each branch here.
- annulus
- keep-out of circle
- stay inside circle.
| return result.squeeze(0) if single_input else result | ||
|
|
||
| @staticmethod | ||
| def _radius_with_lower_bound_escape_direction( |
There was a problem hiding this comment.
Is this a practical scenario? I.e. a scenario that we're likely to run into in situations that are not already degenerate?
Let's talk about this offline.
Summary
PositionLimitswith optional world-XY disk and annulus bounds.relation_loss_weightcompatibility and provide deterministic, tensorized optimization behavior at the exact center of a lower radial bound.Motivation
Rectangular limits cannot directly express a radial placement region. This adds composable disk/annulus constraints without changing legacy rectangular behavior.
Rendered example
Fifty cubes constrained to the intersection of a 30–60 cm annulus and an 80 cm × 80 cm axial box, both centered on the tabletop.