diff --git a/docs/agents/acu_agent.rst b/docs/agents/acu_agent.rst index c02faa020..13e3be5d4 100644 --- a/docs/agents/acu_agent.rst +++ b/docs/agents/acu_agent.rst @@ -90,6 +90,7 @@ example configuration block is below:: 'upper': 360., }, 'acc': (8./1.88), + 'axes_sequential': False, }, } diff --git a/socs/agents/acu/agent.py b/socs/agents/acu/agent.py index c31942a7c..c2edbf716 100644 --- a/socs/agents/acu/agent.py +++ b/socs/agents/acu/agent.py @@ -2118,12 +2118,13 @@ def _reset_sun_params(self, enabled=None, radius=None): _p['active_avoidance'] = config['enabled'] _p['policy'] = config['policy'] - # And add in platform limits + # And add in platform limits and move policies _p['policy'].update({ 'min_az': self.motion_limits['azimuth']['lower'], 'max_az': self.motion_limits['azimuth']['upper'], 'min_el': self.motion_limits['elevation']['lower'], 'max_el': self.motion_limits['elevation']['upper'], + 'axes_sequential': self.motion_limits.get('axes_sequential', False), }) self.sun_params = _p @@ -2598,10 +2599,23 @@ def _get_sunsafe_moves(self, target_az, target_el, zero_legs_ok=True): the target position in it. When Sun avoidance is not enabled, this function returns as - though the direct path to the target is a safe one. + though the direct path to the target is a safe one (though + axes_sequential=True may turn this into a move with two legs). """ + # Get current position. + try: + az, el = [self.data['status']['summary'][f'{ax}_current_position'] + for ax in ['Azimuth', 'Elevation']] + if az is None or el is None: + raise KeyError + except KeyError: + return None, 'Current position could not be determined.' + if not self._get_sun_policy('sunsafe_moves'): + if self.motion_limits.get('axes_sequential'): + # Move in az first, then el. + return [(target_az, el), (target_az, target_el)], None return [(target_az, target_el)], None if not self._get_sun_policy('map_valid'): @@ -2611,15 +2625,6 @@ def _get_sunsafe_moves(self, target_az, target_el, zero_legs_ok=True): if self.sun.check_trajectory([target_az], [target_el])['sun_time'] <= 0: return None, 'Requested target position is not Sun-Safe.' - # Ok, so where are we now ... - try: - az, el = [self.data['status']['summary'][f'{ax}_current_position'] - for ax in ['Azimuth', 'Elevation']] - if az is None or el is None: - raise KeyError - except KeyError: - return None, 'Current position could not be determined.' - moves = self.sun.analyze_paths(az, el, target_az, target_el) move, decisions = self.sun.select_move(moves) if move is None: diff --git a/socs/agents/acu/avoidance.py b/socs/agents/acu/avoidance.py index 9bb96db76..8309e539d 100644 --- a/socs/agents/acu/avoidance.py +++ b/socs/agents/acu/avoidance.py @@ -60,6 +60,12 @@ setting only affects point-to-point motions; "escape" paths will always consider all available elevations. + ``axes_sequential`` + If True, a point-to-point motion will only be accepted if the legs + each move only in az or only in el. When this is False, legs that + move simultaneously in az and el are permitted (and probably + preferred) as long as they are safe. + A "Sun-safe" position is a pointing of the boresight that currently has a ``sun_time`` that meets or exceeds the ``min_sun_time`` @@ -100,6 +106,7 @@ 'el_dodging': False, 'min_sun_time': HOUR, 'response_time': HOUR * 4, + 'axes_sequential': False, } @@ -557,6 +564,10 @@ def reject(d, reason): els = m['req_start'][1], m['req_stop'][1] + if _p['axes_sequential'] and m['moves'].includes_mixed_moves(): + reject(d, 'Path includes simultaneous az+el legs (forbidden in policy).') + continue + if (m['sun_time_start'] < _p['min_sun_time']): # If the path is starting in danger zone, then only # enforce that the move takes the platform to a better place. @@ -688,3 +699,13 @@ def get_traj(self, res=0.5): xx.append(np.linspace(x0, x1, n)) yy.append(np.linspace(y0, y1, n)) return np.hstack(tuple(xx)), np.hstack(tuple(yy)) + + def includes_mixed_moves(self): + """Returns True if any of the legs include simultaneous + changes in az and el. + + """ + for (x0, y0), (x1, y1) in self.get_legs(): + if (x0 != x1) + (y0 != y1) > 1: + return True + return False diff --git a/tests/agents/test_acu_agent.py b/tests/agents/test_acu_agent.py index dc928c3a9..d046b2237 100644 --- a/tests/agents/test_acu_agent.py +++ b/tests/agents/test_acu_agent.py @@ -33,6 +33,14 @@ def test_avoidance(): assert path is not None assert len(path['moves'].nodes) == 2 + # .. even if policy forbids mixed-axis moves + sun.policy['axes_sequential'] = True + paths = sun.analyze_paths(270.01, 40.01, 270, 40) + path, analysis = sun.select_move(paths) + assert path is not None + assert len(path['moves'].nodes) == 3 + del sun.policy['axes_sequential'] + # Find safe paths to here (no moves) paths = sun.analyze_paths(270, 40, 270, 40) path, analysis = sun.select_move(paths)