-
Notifications
You must be signed in to change notification settings - Fork 86
Add dataclass and state machine for fine grained subtask tracking #758
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
Merged
Merged
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
0cf1f5f
add fine grained subtask data class and state machine
peterd-NV a510bf7
update fgs tests
peterd-NV 6b734ff
update task base and composite task base with fgs
peterd-NV 231db88
update get_fgs in composite tasks
peterd-NV 4c9aa2b
update state machine naming
peterd-NV ddbb730
update state machine file name
peterd-NV b394b09
update naming from subtasks to progress_tracking
peterd-NV 83f0ca3
Merge branch 'main' of github.com:isaac-sim/IsaacLab-Arena into peter…
peterd-NV de73e3d
move from step hook from terminations mgr to recorder mgr
peterd-NV 680fa6d
address comments for readbility in state machine
peterd-NV 24807a4
perf improvement in reset and resolve object lists
peterd-NV 00693fb
Merge branch 'main' of github.com:isaac-sim/IsaacLab-Arena into peter…
peterd-NV b343a8b
Merge branch 'main' of github.com:isaac-sim/IsaacLab-Arena into peter…
peterd-NV cd88773
move progress tracking to it's own module
peterd-NV 91080a2
fix copyright years
peterd-NV c2bdbde
Remove FineGrained* naming and add fixes for review comments
peterd-NV 5a815b5
Merge branch 'main' of github.com:isaac-sim/IsaacLab-Arena into peter…
peterd-NV 3a5bfad
remove statemachine term from docstring
peterd-NV 61a0134
update copyright year
peterd-NV 7f40e69
address comments
peterd-NV 8d4b8b3
fix tests and address review comments
peterd-NV 7a3b3b1
create dataclasses for state to repalce dicts
peterd-NV d5ba6dd
lint
peterd-NV c37c3f5
handle tensor input bug in progress tracker reset
peterd-NV de4de42
Merge branch 'main' of github.com:isaac-sim/IsaacLab-Arena into peter…
peterd-NV e4c9f10
Merge branch 'main' of github.com:isaac-sim/IsaacLab-Arena into peter…
peterd-NV File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | ||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
177 changes: 177 additions & 0 deletions
177
isaaclab_arena/progress_tracking/fine_grained_progress_objective.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| # Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | ||
|
peterd-NV marked this conversation as resolved.
Outdated
|
||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Callable | ||
| from dataclasses import dataclass, field | ||
| from typing import Literal, Union | ||
|
|
||
| PredicateGroups = Union[ | ||
| Callable, | ||
| list[Callable], | ||
| list[tuple[Callable, float]], | ||
| dict[str, Callable], | ||
| dict[str, list[Callable]], | ||
| dict[str, list[tuple[Callable, float]]], | ||
| ] | ||
|
|
||
|
|
||
| DEFAULT_GROUP_NAME = "default_group" | ||
|
|
||
|
|
||
| def format_predicate_groups(predicate_groups: PredicateGroups) -> dict[str, list[tuple[Callable, float]]]: | ||
| """Format predicate_groups into the canonical form. | ||
|
|
||
| Canonical form: ``dict[group_name: list[(callable, score)]]``. | ||
|
|
||
| Accepted input shapes: | ||
| 1. func (single callable) one group with one predicate | ||
| 2. [func, func, ...] one group, sequential chain | ||
| 3. [(func, score), ...] one group, sequential chain, weighted | ||
| 4. {group: func} multiple groups, one predicate each | ||
| 5. {group: [func, ...]} multiple groups, sequential chains | ||
| 6. {group: [(func, score), ...]} multiple groups, sequential chains, weighted | ||
| """ | ||
|
|
||
| if callable(predicate_groups): | ||
| return {DEFAULT_GROUP_NAME: [(predicate_groups, 1.0)]} | ||
|
|
||
| if isinstance(predicate_groups, list): | ||
| if len(predicate_groups) == 0: | ||
| raise ValueError("FineGrainedProgressObjective.predicate_groups list cannot be empty") | ||
|
peterd-NV marked this conversation as resolved.
Outdated
|
||
| return {DEFAULT_GROUP_NAME: _format_group_chain(predicate_groups, group_name=DEFAULT_GROUP_NAME)} | ||
|
|
||
| if isinstance(predicate_groups, dict): | ||
| if len(predicate_groups) == 0: | ||
| raise ValueError("FineGrainedProgressObjective.predicate_groups dict cannot be empty") | ||
| return { | ||
| group_name: _format_group_chain(value, group_name=group_name) | ||
| for group_name, value in predicate_groups.items() | ||
| } | ||
|
|
||
| raise TypeError( | ||
| "FineGrainedProgressObjective.predicate_groups must be a callable, list, or dict; got" | ||
| f" {type(predicate_groups).__name__}" | ||
| ) | ||
|
|
||
|
|
||
| def _format_group_chain(value, group_name: str) -> list[tuple[Callable, float]]: | ||
| if callable(value): | ||
| return [(value, 1.0)] | ||
| if not isinstance(value, list): | ||
| raise TypeError( | ||
| f"Predicate chain for group '{group_name}' must be a callable or a list; got {type(value).__name__}" | ||
| ) | ||
| if len(value) == 0: | ||
| raise ValueError(f"Predicate chain for group '{group_name}' cannot be empty") | ||
|
|
||
| first = value[0] | ||
| if isinstance(first, tuple): | ||
| chain = [] | ||
| for i, item in enumerate(value): | ||
| if not (isinstance(item, tuple) and len(item) == 2): | ||
| raise TypeError(f"Group '{group_name}' index {i}: expected (callable, score) tuple, got {item!r}") | ||
| fn, score = item | ||
| if not callable(fn): | ||
| raise TypeError(f"Group '{group_name}' index {i}: first tuple element must be callable") | ||
| if not isinstance(score, (int, float)): | ||
| raise TypeError(f"Group '{group_name}' index {i}: score must be a number") | ||
| chain.append((fn, float(score))) | ||
| return chain | ||
|
|
||
| if callable(first): | ||
| equal = 1.0 / len(value) | ||
| chain = [] | ||
| for i, fn in enumerate(value): | ||
| if not callable(fn): | ||
| raise TypeError(f"Group '{group_name}' index {i}: expected callable, got {type(fn).__name__}") | ||
| chain.append((fn, equal)) | ||
| return chain | ||
|
|
||
| raise TypeError( | ||
| f"Group '{group_name}' elements must be callables or (callable, score) tuples; got {type(first).__name__}" | ||
| ) | ||
|
|
||
|
|
||
| def normalize_scores( | ||
| predicate_groups: dict[str, list[tuple[Callable, float]]], | ||
| ) -> dict[str, list[tuple[Callable, float]]]: | ||
| """Scale each group's scores to sum to 1.0. Zero and negative-sum groups are left untouched.""" | ||
|
|
||
| out: dict[str, list[tuple[Callable, float]]] = {} | ||
| for group, chain in predicate_groups.items(): | ||
| total = sum(score for _, score in chain) | ||
| if total <= 0: | ||
| out[group] = list(chain) | ||
| continue | ||
| out[group] = [(fn, score / total) for fn, score in chain] | ||
| return out | ||
|
|
||
|
|
||
| @dataclass | ||
| class FineGrainedProgressObjective: | ||
| """Configuration object that defines a scored predicate sequence to track progress within a task. | ||
|
|
||
| A FineGrainedProgressObjective specifies what the predicate state machine should track. | ||
| Each FineGrainedProgressObjective holds one or more sequential predicate chains (groups). | ||
| Within a group, predicates run in order. Across groups, predicates run in parallel. | ||
|
|
||
| Args: | ||
| name: Identifies the FineGrainedProgressObjective within the TaskBase. | ||
| predicate_groups: The sequential predicate chains that define the FineGrainedProgressObjective. | ||
| score: Weight of the FineGrainedProgressObjective in the TaskBase-level overall_score. | ||
| logical: How completed groups combine to determine if the FineGrainedProgressObjective is complete. | ||
| Can be "all", "any", or "choose" | ||
| K: Required when logical == "choose". Specifies the number of groups that must be completed | ||
| to consider the FineGrainedProgressObjective complete. | ||
| description: An optional description of the FineGrainedProgressObjective. | ||
| """ | ||
|
|
||
| name: str | ||
| predicate_groups: PredicateGroups | ||
| score: float = 1.0 | ||
| logical: Literal["all", "any", "choose"] = "all" | ||
| K: int | None = None | ||
| description: str | None = None | ||
|
|
||
| canonical_predicate_groups: dict[str, list[tuple[Callable, float]]] = field(init=False, repr=False) | ||
|
|
||
| # Index of the parent TaskBase this recipe belongs to. Set automatically by | ||
|
peterd-NV marked this conversation as resolved.
Outdated
|
||
| # CompositeTaskBase.get_fine_grained_progress_objectives() when used with composite tasks. | ||
| parent_subtask_idx: int | None = None | ||
|
|
||
| def __post_init__(self): | ||
| if not (0.0 <= self.score <= 1.0): | ||
| raise ValueError(f"FineGrainedProgressObjective '{self.name}': score must be in [0, 1], got {self.score}") | ||
| if self.logical not in ("all", "any", "choose"): | ||
| raise ValueError( | ||
| f"FineGrainedProgressObjective '{self.name}': logical must be in ['all', 'any', 'choose'], got" | ||
| f" {self.logical}" | ||
| ) | ||
|
|
||
| # Format the predicate groups into the canonical form and normalize the scores. | ||
| formatted = format_predicate_groups(self.predicate_groups) | ||
| normalized = normalize_scores(formatted) | ||
| self.canonical_predicate_groups = normalized | ||
|
|
||
| # Validate the logical and K parameters. | ||
| num_groups = len(self.canonical_predicate_groups) | ||
| if self.logical == "choose": | ||
| if self.K is None: | ||
| raise ValueError(f"FineGrainedProgressObjective '{self.name}': K is required when logical='choose'") | ||
| if not (1 <= self.K <= num_groups): | ||
| raise ValueError( | ||
| f"FineGrainedProgressObjective '{self.name}': K={self.K} but must be in [1, {num_groups}]" | ||
|
peterd-NV marked this conversation as resolved.
Outdated
|
||
| ) | ||
|
|
||
| @property | ||
| def group_names(self) -> list[str]: | ||
| """Returns the names of the groups in the FineGrainedProgressObjective.""" | ||
| return list(self.canonical_predicate_groups.keys()) | ||
|
|
||
| def get_chain(self, group_name: str) -> list[tuple[Callable, float]]: | ||
| """Returns the chain of predicates for a given group.""" | ||
| return self.canonical_predicate_groups[group_name] | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.