From a38c3c61c02bd30e6da042a0aeedc831cb67e3ad Mon Sep 17 00:00:00 2001 From: Matthew Hasselfield Date: Thu, 31 Oct 2024 12:19:32 -0400 Subject: [PATCH] ACU: "axes_sequential" config setting, to force separate az/el moves (#778) If enabled, this applies whether Sun Avoidance is on, or not. But special handling is applied, for case of Sun Avoidance. --- docs/agents/acu_agent.rst | 1 + socs/agents/acu/agent.py | 27 ++++++++++++++++----------- socs/agents/acu/avoidance.py | 21 +++++++++++++++++++++ tests/agents/test_acu_agent.py | 8 ++++++++ 4 files changed, 46 insertions(+), 11 deletions(-) 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..34924c752 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 each leg + is a constant el or constant az move. When this setting 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) and (y0 != y1): + return True + return False diff --git a/tests/agents/test_acu_agent.py b/tests/agents/test_acu_agent.py index dc928c3a9..b088dc203 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 + sun.policy['axes_sequential'] = False + # Find safe paths to here (no moves) paths = sun.analyze_paths(270, 40, 270, 40) path, analysis = sun.select_move(paths)