@@ -64,9 +64,13 @@ def __init__(self, options: Set[ParameterizedOption],
6464 super ().__init__ ()
6565 self ._name_to_parameterized_option = {o .name : o for o in options }
6666 self ._simulator = simulator
67+ # Diagnostic: stores the reason when the last call returned 0 actions.
68+ self .last_execution_failure : str | None = None
6769
6870 def get_next_state_and_num_actions (self , state : State ,
6971 option : _Option ) -> Tuple [State , int ]:
72+ self .last_execution_failure = None
73+
7074 # We do not want to actually execute the option; we want to know what
7175 # *would* happen if we were to execute the option. So, we will make a
7276 # copy of the option and run that instead. This is important if the
@@ -109,7 +113,13 @@ def _terminal(s: State) -> bool:
109113 return True
110114 if last_state is not DefaultState and last_state .allclose (s ):
111115 logging .debug ("Option got stuck." )
112- raise utils .OptionExecutionFailure ("Option got stuck." )
116+ raise utils .OptionExecutionFailure (
117+ f"Option '{ option_copy .name } ' got stuck: the "
118+ f"policy's action did not change the state. "
119+ f"This usually means the first motion phase "
120+ f"produced a no-op (e.g. IK returned current "
121+ f"joints, or finger command matched current "
122+ f"finger state)." )
113123 last_state = s
114124 return False
115125 else :
@@ -123,9 +133,10 @@ def _terminal(s: State) -> bool:
123133 state ,
124134 _terminal ,
125135 max_num_steps = CFG .max_num_steps_option_rollout )
126- except utils .OptionExecutionFailure :
136+ except utils .OptionExecutionFailure as e :
127137 # If there is a failure during the execution of the option, treat
128138 # this as a noop.
139+ self .last_execution_failure = str (e )
129140 return state , 0
130141 # Note that in the case of using a PyBullet environment, the
131142 # second return value (num_actions) will be an underestimate
0 commit comments