From 9e04bf353a263050431c39e34e1f41ea7948c694 Mon Sep 17 00:00:00 2001 From: Matthew Hasselfield Date: Mon, 21 Oct 2024 13:44:34 -0400 Subject: [PATCH] ACU: acu-config setting for forcing sequential moves E.g. if you generally don't want to move az and el axes simultaneously. 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..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)