From 7880d1562397f3c96588bb29d800cda896d43c27 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Mon, 18 Nov 2024 15:25:52 -0300 Subject: [PATCH 01/17] Add a move_p2p_diamond.py script to run TMA dynamic tests --- .../scripts/maintel/tma/move_p2p_diamond.py | 26 ++ .../externalscripts/maintel/tma/__init__.py | 1 + .../maintel/tma/move_p2p_diamond.py | 258 ++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100755 python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py create mode 100755 python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py diff --git a/python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py new file mode 100755 index 000000000..30b911cdc --- /dev/null +++ b/python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# This file is part of ts_externalscripts +# +# Developed for the LSST Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License + +import asyncio + +from lsst.ts.externalscripts.maintel.tma import MoveP2PDiamond + +asyncio.run(MoveP2PDiamond.amain()) diff --git a/python/lsst/ts/externalscripts/maintel/tma/__init__.py b/python/lsst/ts/externalscripts/maintel/tma/__init__.py index 85e418f6a..cafb015bf 100644 --- a/python/lsst/ts/externalscripts/maintel/tma/__init__.py +++ b/python/lsst/ts/externalscripts/maintel/tma/__init__.py @@ -19,6 +19,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from .move_p2p_diamond import * from .random_walk import * from .random_walk_and_take_image_gencam import * from .serpent_walk import * diff --git a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py new file mode 100755 index 000000000..17799ae09 --- /dev/null +++ b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py @@ -0,0 +1,258 @@ +# This file is part of ts_externalscripts +# +# Developed for the LSST Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License + +__all__ = ["MoveP2PDiamond"] + +import asyncio + +import yaml +from lsst.ts.idl.enums.Script import ScriptState +from lsst.ts.observatory.control.maintel.mtcs import MTCS, MTCSUsages +from lsst.ts.salobj import type_hints +from lsst.ts.standardscripts.base_block_script import BaseBlockScript + + +class MoveP2PDiamond(BaseBlockScript): + """Moves the telescope in a diamond pattern around each grid position.""" + + def __init__(self, index: int) -> None: + super().__init__(index, descr="Move telescope in diamond pattern around grid.") + self.mtcs = None + self.grid = dict() + self.pause_for = 0.0 + self.move_timeout = 120.0 + + self.LONG_SLEW_AZ = 24.0 # degrees + self.LONG_SLEW_EL = 12.0 # degrees + self.SHORT_SLEW_AZ = 3.5 # degrees + self.SHORT_SLEW_EL = 3.5 # degrees + + # Telescope limits + self.max_el = 86.5 + self.min_el = 15 + self.max_az = 180 + self.min_az = -180 + + # This will hold all the precomputed diamond sequences + self.diamond_sequences = [] + + async def configure_tcs(self) -> None: + """Initialize the MTCS component if not already done.""" + if self.mtcs is None: + self.log.debug("Creating MTCS.") + self.mtcs = MTCS( + domain=self.domain, intended_usage=MTCSUsages.Slew, log=self.log + ) + await self.mtcs.start_task + else: + self.log.debug("MTCS already defined, skipping.") + + @classmethod + def get_schema(cls): + # You can retain or update the schema as before + schema_yaml = """ + $schema: http://json-schema.org/draft-07/schema# + title: MoveDiamondPattern Configuration + type: object + properties: + grid_az: + description: List of azimuth coordinates in degrees. + anyOf: + - type: number + - type: array + items: + type: number + minItems: 1 + grid_el: + description: List of elevation coordinates in degrees. + anyOf: + - type: number + - type: array + items: + type: number + minItems: 1 + pause_for: + description: Pause duration between movements in seconds. + type: number + default: 0.0 + move_timeout: + description: Timeout for each move command. + type: number + default: 120.0 + ignore: + description: CSCs to ignore in status check. + type: array + items: + type: string + required: + - grid_az + - grid_el + """ + return yaml.safe_load(schema_yaml) + + async def configure(self, config): + """Configures the script based on user-defined grid and settings.""" + + # Ensure grid_az and grid_el are arrays + grid_az = ( + config.grid_az if isinstance(config.grid_az, list) else [config.grid_az] + ) + grid_el = ( + config.grid_el if isinstance(config.grid_el, list) else [config.grid_el] + ) + + self.grid["azel"] = dict(az=grid_az, el=grid_el) + self.pause_for = config.pause_for + self.move_timeout = config.move_timeout + + # Generate and validate all positions + self.generate_and_validate_positions() + + await self.configure_tcs() + for comp in getattr(config, "ignore", []): + if comp not in self.mtcs.components_attr: + self.log.warning(f"Ignoring unknown component {comp}.") + else: + self.log.debug(f"Ignoring component {comp}.") + setattr(self.mtcs.check, comp, False) + + await super().configure(config=config) + + def set_metadata(self, metadata: type_hints.BaseMsgType) -> None: + """Set the estimated duration based on the number of positions.""" + num_positions = sum(len(seq["positions"]) for seq in self.diamond_sequences) + estimated_duration = num_positions * (self.move_timeout + self.pause_for) + metadata.duration = estimated_duration + + def generate_diamond_pattern(self, az0, el0): + """ + Generate a diamond pattern of azimuth and elevation coordinates. + + Parameters: + az0 (float): Initial azimuth coordinate. + el0 (float): Initial elevation coordinate. + + Returns: + list of tuple: A list of tuples where each tuple contains an azimuth + and elevation coordinate. + + Notes: + The diamond pattern created here aims to reproduce the pattern used + for dynamic tests done under BLOCK-T227, T293 abd T294 + """ + + # Define the slew offsets for the diamond pattern to match dynamic + # tests done under BLOCK-T227, T293, T294 + azel_slew_offsets = [ + (0, 0), + (0, +self.LONG_SLEW_EL), + (0, -self.LONG_SLEW_EL), + (+self.LONG_SLEW_AZ, 0), + (-self.LONG_SLEW_AZ, 0), + (0, +self.SHORT_SLEW_EL), + (0, -self.SHORT_SLEW_EL), + (+self.SHORT_SLEW_AZ, 0), + (-self.SHORT_SLEW_AZ, 0), + (+self.LONG_SLEW_AZ / 2 / (2**0.5), +self.LONG_SLEW_EL / (2**0.5)), + (-self.LONG_SLEW_AZ / 2 / (2**0.5), +self.LONG_SLEW_EL / (2**0.5)), + (-self.LONG_SLEW_AZ / 2 / (2**0.5), -self.LONG_SLEW_EL / (2**0.5)), + (+self.LONG_SLEW_AZ / 2 / (2**0.5), -self.LONG_SLEW_EL / (2**0.5)), + (+self.SHORT_SLEW_AZ / (2**0.5), +self.SHORT_SLEW_EL / (2**0.5)), + (-self.SHORT_SLEW_AZ / (2**0.5), +self.SHORT_SLEW_EL / (2**0.5)), + (-self.SHORT_SLEW_AZ / (2**0.5), -self.SHORT_SLEW_EL / (2**0.5)), + (+self.SHORT_SLEW_AZ / (2**0.5), -self.SHORT_SLEW_EL / (2**0.5)), + ] + + az = az0 + el = el0 + positions = [] + for az_offset, el_offset in azel_slew_offsets: + az += az_offset + el += el_offset + positions.append((round(az, 2), round(el, 2))) + return positions + + def generate_and_validate_positions(self): + """ + Generate all positions for the grid points and their diamond patterns, + validate them, and store them for later use. + """ + self.all_positions = [] + + for az0 in self.grid["azel"]["az"]: + for el0 in self.grid["azel"]["el"]: + positions = self.generate_diamond_pattern(az0, el0) + for az, el in positions: + if not (self.min_az <= az <= self.max_az): + raise ValueError( + f"Azimuth {az} out of limits ({self.min_az}, {self.max_az}). " + f"Ensure that the entire movement range stays within the " + f"allowed azimuth limits. Adjust initial azimuth (Az = {az0}) " + f"in your grid accordingly." + ) + if not (self.min_el <= el <= self.max_el): + raise ValueError( + f"Elevation {el} out of limits ({self.min_el}, {self.max_el}). " + f"Ensure that the entire movement range stays within the allowed " + f"elevation limits. Adjust initial elevation (El = {el0}) in your " + f"grid accordingly." + ) + # Add the sequence to the list + self.diamond_sequences.append( + {"az0": az0, "el0": el0, "positions": positions} + ) + + async def move_to_position(self, az, el): + """Move the telescope to the specified azimuth and elevation.""" + self.log.debug(f"Moving telescope to az={az}, el={el}.") + await self.mtcs.move_p2p_azel(az=az, el=el, timeout=self.move_timeout) + + async def run_block(self): + """Execute the precomputed positions.""" + total_diamonds = len(self.diamond_sequences) + for i, sequence in enumerate(self.diamond_sequences): + az0 = sequence["az0"] + el0 = sequence["el0"] + positions = sequence["positions"] + # Output checkpoint message + await self.checkpoint( + f"Starting diamond sequence {i+1}/{total_diamonds} at grid point (Az={az0}, El={el0})" + ) + total_positions = len(positions) + for j, (az, el) in enumerate(positions, start=1): + self.log.info( + f"Moving to position {j}/{total_positions} in diamond sequence {i+1}: Az={az}, El={el}" + ) + await self.move_to_position(az, el) + self.log.info(f"Pausing for {self.pause_for}s.") + await asyncio.sleep(self.pause_for) + + async def cleanup(self): + """Handle cleanup in case of abnormal termination.""" + if self.state.state != ScriptState.ENDING: + self.log.warning("Terminating abnormally, stopping telescope.") + try: + await self.mtcs.rem.mtmount.cmd_stop.start( + timeout=self.mtcs.long_timeout + ) + except asyncio.TimeoutError: + self.log.exception("Stop command timed out during cleanup.") + except Exception: + self.log.exception("Unexpected error during telescope stop.") From 890293a718325fa5a939e6efb5de2b2913bbaae8 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Mon, 18 Nov 2024 15:26:28 -0300 Subject: [PATCH 02/17] Add unit test for move_p2p_diamond.py --- tests/maintel/tma/test_move_p2p_diamond.py | 166 +++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 tests/maintel/tma/test_move_p2p_diamond.py diff --git a/tests/maintel/tma/test_move_p2p_diamond.py b/tests/maintel/tma/test_move_p2p_diamond.py new file mode 100644 index 000000000..56af71ee8 --- /dev/null +++ b/tests/maintel/tma/test_move_p2p_diamond.py @@ -0,0 +1,166 @@ +# This file is part of ts_standardscripts +# +# Developed for the LSST Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify + +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License + +import contextlib +import unittest +from unittest.mock import AsyncMock, MagicMock + +import pytest +from lsst.ts import externalscripts, salobj, standardscripts +from lsst.ts.externalscripts.maintel.tma import MoveP2PDiamond + + +class TestMoveP2PDiamond( + standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase +): + async def basic_make_script(self, index): + self.script = MoveP2PDiamond(index=index) + return (self.script,) + + @contextlib.asynccontextmanager + async def make_dry_script(self): + async with self.make_script(): + # Mock the mtcs component + self.script.mtcs = AsyncMock() + self.script.mtcs.components_attr = ["mtm1m3"] + # Mock the mtcs.check object + self.script.mtcs.check = MagicMock() + + yield + + async def test_config_ignore(self) -> None: + async with self.make_dry_script(): + grid_az = 0.0 + grid_el = 60.0 + + await self.configure_script( + grid_az=grid_az, grid_el=grid_el, ignore=["mtm1m3", "no_comp"] + ) + assert self.script.mtcs.check.mtm1m3 is False + self.script.mtcs.check.no_comp.assert_not_called() + + async def test_config_grid_az_el_scalars(self) -> None: + async with self.make_dry_script(): + grid_az = 30.0 + grid_el = 60.0 + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + ) + assert self.script.grid["azel"]["az"] == [grid_az] + assert self.script.grid["azel"]["el"] == [grid_el] + assert self.script.pause_for == 0.0 + assert self.script.move_timeout == 120.0 + + async def test_config_az_el_arrays(self) -> None: + async with self.make_dry_script(): + grid_az = [30.0] + grid_el = [45.0, 45.0, 35.0] + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + ) + # assert config_tcs was awaited once + assert self.script.grid["azel"]["az"] == grid_az + assert self.script.grid["azel"]["el"] == grid_el + assert self.script.pause_for == 0.0 + assert self.script.move_timeout == 120.0 + + async def test_config_pause_for_move_timeout(self) -> None: + async with self.make_dry_script(): + grid_az = 30.0 + grid_el = [60.0] + pause_for = 10.0 + move_timeout = 200.0 + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + pause_for=pause_for, + move_timeout=move_timeout, + ) + assert self.script.pause_for == pause_for + assert self.script.move_timeout == move_timeout + + async def test_az_outside_limits(self): + async with self.make_dry_script(): + # Use an azimuth value beyond the maximum limit + grid_az = self.script.max_az + 10 # Exceeds max azimuth limit + grid_el = 60.0 # Valid elevation within limits + with pytest.raises( + salobj.ExpectedError, match=f"Azimuth {grid_az} out of limits" + ): + await self.configure_script(grid_az=grid_az, grid_el=grid_el) + + async def test_el_outside_limits(self): + async with self.make_dry_script(): + grid_az = 30.0 # Valid azimuth within limits + # Use an elevation value below the minimum limit + grid_el = self.script.min_el - 5 # Below min elevation limit + with pytest.raises( + salobj.ExpectedError, match=f"Elevation {grid_el} out of limits" + ): + await self.configure_script(grid_az=grid_az, grid_el=grid_el) + + async def test_run_block(self): + async with self.make_dry_script(): + grid_az = [-75] + grid_el = [35.0, 55.0] + pause_for = 1.0 + move_timeout = 120.0 + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + pause_for=pause_for, + move_timeout=move_timeout, + ) + + # Mock move_to_position to prevent actual calls + self.script.move_to_position = AsyncMock() + # Mock checkpoint to prevent actual calls + self.script.checkpoint = AsyncMock() + + await self.script.run_block() + + # Calculate expected number of diamond sequences and positions + total_sequences = len(grid_az) * len(grid_el) + total_positions_per_sequence = len( + self.script.generate_diamond_pattern(0, 0) + ) + expected_total_positions = total_sequences * total_positions_per_sequence + + # Verify checkpoint messages + assert self.script.checkpoint.call_count == total_sequences + + # Verify move_to_position calls + assert self.script.move_to_position.await_count == expected_total_positions + + # Optionally, check the specific calls + expected_calls = [] + for sequence in self.script.diamond_sequences: + for position in sequence["positions"]: + expected_calls.append(unittest.mock.call(position[0], position[1])) + self.script.move_to_position.assert_has_awaits(expected_calls) + + async def test_executable(self): + scripts_dir = externalscripts.get_scripts_dir() + script_path = scripts_dir / "maintel" / "move_p2p_diamond.py" + self.log.debug(f"Checking for script in {script_path}") + await self.check_executable(script_path) From 982116e3c8a7cbfbfd3dbd5da67ab9472435da3f Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Mon, 18 Nov 2024 15:50:45 -0300 Subject: [PATCH 03/17] Add news fragment --- doc/news/DM-47627.feature.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 doc/news/DM-47627.feature.rst diff --git a/doc/news/DM-47627.feature.rst b/doc/news/DM-47627.feature.rst new file mode 100644 index 000000000..707c471aa --- /dev/null +++ b/doc/news/DM-47627.feature.rst @@ -0,0 +1,2 @@ +Add a `move_p2p_diamond.py` script. +The script moves the Simonyi Telescope in a diamond pattern around each grid position provided by the user. \ No newline at end of file From 60a7466c8120e1fee4115829ee5445ba2ef343c1 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Mon, 18 Nov 2024 17:04:26 -0300 Subject: [PATCH 04/17] Fix path for script dir in unit test --- tests/maintel/tma/test_move_p2p_diamond.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/maintel/tma/test_move_p2p_diamond.py b/tests/maintel/tma/test_move_p2p_diamond.py index 56af71ee8..cf85371a9 100644 --- a/tests/maintel/tma/test_move_p2p_diamond.py +++ b/tests/maintel/tma/test_move_p2p_diamond.py @@ -161,6 +161,6 @@ async def test_run_block(self): async def test_executable(self): scripts_dir = externalscripts.get_scripts_dir() - script_path = scripts_dir / "maintel" / "move_p2p_diamond.py" + script_path = scripts_dir / "maintel" / "tma" / "move_p2p_diamond.py" self.log.debug(f"Checking for script in {script_path}") await self.check_executable(script_path) From 9d6b6140d02086244d86063d68626644b0c1fde4 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Mon, 18 Nov 2024 18:36:32 -0300 Subject: [PATCH 05/17] Remove unnecessary debug message in unit test --- tests/maintel/tma/test_move_p2p_diamond.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/maintel/tma/test_move_p2p_diamond.py b/tests/maintel/tma/test_move_p2p_diamond.py index cf85371a9..6fdd05e68 100644 --- a/tests/maintel/tma/test_move_p2p_diamond.py +++ b/tests/maintel/tma/test_move_p2p_diamond.py @@ -162,5 +162,4 @@ async def test_run_block(self): async def test_executable(self): scripts_dir = externalscripts.get_scripts_dir() script_path = scripts_dir / "maintel" / "tma" / "move_p2p_diamond.py" - self.log.debug(f"Checking for script in {script_path}") await self.check_executable(script_path) From 360488b7325c360cb8a0584f9cfa5a16a371138a Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Mon, 18 Nov 2024 21:55:53 -0300 Subject: [PATCH 06/17] Remove no longer needed array in main script --- python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py index 17799ae09..ceafeb0b5 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py +++ b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py @@ -194,7 +194,6 @@ def generate_and_validate_positions(self): Generate all positions for the grid points and their diamond patterns, validate them, and store them for later use. """ - self.all_positions = [] for az0 in self.grid["azel"]["az"]: for el0 in self.grid["azel"]["el"]: From 8e22897aef3a26c04b00703fe41867bdde161d21 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Tue, 19 Nov 2024 11:47:40 -0300 Subject: [PATCH 07/17] Improve documentation --- .../maintel/tma/move_p2p_diamond.py | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py index ceafeb0b5..fe96eea5b 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py +++ b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py @@ -30,7 +30,32 @@ class MoveP2PDiamond(BaseBlockScript): - """Moves the telescope in a diamond pattern around each grid position.""" + """Moves the telescope in a diamond pattern around each grid position. + + Overview: + + This script performs a series of point-to-point (P2P) telescope + movements forming a diamond pattern around each user-defined grid + position. It is designed for dynamic tests and performance evaluations, + reproducing patterns used in specific engineering tasks (e.g., BLOCK-T227, + T293, T294). + + Execution Order: + + The script processes the grid positions by iterating over `grid_az` + first, then `grid_el`. For each azimuth value in `grid_az`, it executes + the diamond patterns at all elevation values in `grid_el` in order. + + User Guidance: + + - Grid Selection: Choose `grid_az` and `grid_el` values within the + telescope's operational limits. Ensure that cumulative movements + from the diamond pattern do not exceed these limits. + - Understanding the Pattern: The diamond pattern consists of cumulative + offsets applied to the initial position. Familiarity with the pattern + helps in anticipating telescope movements. + - Operational Limits: Azimuth: -180° to +180°, Elevation: 15° to 86.5°. + """ def __init__(self, index: int) -> None: super().__init__(index, descr="Move telescope in diamond pattern around grid.") @@ -73,7 +98,11 @@ def get_schema(cls): type: object properties: grid_az: - description: List of azimuth coordinates in degrees. + description: > + Azimuth coordinate(s) in degrees where the diamond patterns will be executed. + Can be a single number or a list of numbers. Ensure that the azimuth values, + along with the cumulative offsets from the diamond pattern, remain within + the telescope's operational limits. anyOf: - type: number - type: array @@ -81,13 +110,17 @@ def get_schema(cls): type: number minItems: 1 grid_el: - description: List of elevation coordinates in degrees. - anyOf: - - type: number - - type: array - items: - type: number - minItems: 1 + description: > + Elevation coordinate(s) in degrees where the diamond patterns will be executed. + Can be a single number or a list of numbers. Ensure that the elevation values, + along with the cumulative offsets from the diamond pattern, remain within + the telescope's operational limits + anyOf: + - type: number + - type: array + items: + type: number + minItems: 1 pause_for: description: Pause duration between movements in seconds. type: number @@ -150,8 +183,15 @@ def generate_diamond_pattern(self, az0, el0): el0 (float): Initial elevation coordinate. Returns: - list of tuple: A list of tuples where each tuple contains an azimuth - and elevation coordinate. + - `positions` (list of tuple): List of positions forming + the diamond patterns. + + Pattern Details: + - The pattern consists of cumulative movements starting from the + initial position `(az0, el0)`. + - Movements include long and short slews in azimuth and elevation, + as well as diagonal movements. + - The sequence is designed to test the telescope's dynamic performance. Notes: The diamond pattern created here aims to reproduce the pattern used From fa2dd8cf4011fc20b8d9291a910b340d2fc1f696 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 22 Nov 2024 14:56:29 -0300 Subject: [PATCH 08/17] Update unit test --- tests/maintel/tma/test_move_p2p_diamond.py | 117 ++++++++++++++++----- 1 file changed, 91 insertions(+), 26 deletions(-) diff --git a/tests/maintel/tma/test_move_p2p_diamond.py b/tests/maintel/tma/test_move_p2p_diamond.py index 6fdd05e68..0dafe8b01 100644 --- a/tests/maintel/tma/test_move_p2p_diamond.py +++ b/tests/maintel/tma/test_move_p2p_diamond.py @@ -57,6 +57,54 @@ async def test_config_ignore(self) -> None: assert self.script.mtcs.check.mtm1m3 is False self.script.mtcs.check.no_comp.assert_not_called() + async def test_config_directions(self) -> None: + """Test that the direction parameter is correctly set + and positions differ.""" + async with self.make_dry_script(): + grid_az = 30.0 + grid_el = 60.0 + + # Test with direction='forward' + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + direction="forward", + ) + assert self.script.direction == "forward" + positions_forward = self.script.generate_diamond_pattern( + az0=grid_az, el0=grid_el + ) + + # Test with direction='backward' + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + direction="backward", + ) + assert self.script.direction == "backward" + positions_backward = self.script.generate_diamond_pattern( + az0=grid_az, el0=grid_el + ) + + # The positions should not be the same + assert positions_forward != positions_backward + + # Compare the movement deltas to ensure they are opposite + for i in range(1, len(positions_forward)): + az_fwd_prev, el_fwd_prev = positions_forward[i - 1] + az_fwd_curr, el_fwd_curr = positions_forward[i] + delta_az_fwd = az_fwd_curr - az_fwd_prev + delta_el_fwd = el_fwd_curr - el_fwd_prev + + az_bwd_prev, el_bwd_prev = positions_backward[i - 1] + az_bwd_curr, el_bwd_curr = positions_backward[i] + delta_az_bwd = az_bwd_curr - az_bwd_prev + delta_el_bwd = el_bwd_curr - el_bwd_prev + + # Check that the movement deltas are opposite + assert delta_az_fwd == pytest.approx(-delta_az_bwd) + assert delta_el_fwd == pytest.approx(-delta_el_bwd) + async def test_config_grid_az_el_scalars(self) -> None: async with self.make_dry_script(): grid_az = 30.0 @@ -69,6 +117,7 @@ async def test_config_grid_az_el_scalars(self) -> None: assert self.script.grid["azel"]["el"] == [grid_el] assert self.script.pause_for == 0.0 assert self.script.move_timeout == 120.0 + assert self.script.direction == "forward" # Default value async def test_config_az_el_arrays(self) -> None: async with self.make_dry_script(): @@ -132,32 +181,48 @@ async def test_run_block(self): move_timeout=move_timeout, ) - # Mock move_to_position to prevent actual calls - self.script.move_to_position = AsyncMock() - # Mock checkpoint to prevent actual calls - self.script.checkpoint = AsyncMock() - - await self.script.run_block() - - # Calculate expected number of diamond sequences and positions - total_sequences = len(grid_az) * len(grid_el) - total_positions_per_sequence = len( - self.script.generate_diamond_pattern(0, 0) - ) - expected_total_positions = total_sequences * total_positions_per_sequence - - # Verify checkpoint messages - assert self.script.checkpoint.call_count == total_sequences - - # Verify move_to_position calls - assert self.script.move_to_position.await_count == expected_total_positions - - # Optionally, check the specific calls - expected_calls = [] - for sequence in self.script.diamond_sequences: - for position in sequence["positions"]: - expected_calls.append(unittest.mock.call(position[0], position[1])) - self.script.move_to_position.assert_has_awaits(expected_calls) + for direction in ["forward", "backward"]: + await self.configure_script( + grid_az=grid_az, + grid_el=grid_el, + pause_for=pause_for, + move_timeout=move_timeout, + direction=direction, + ) + + # Mock move_to_position to prevent actual calls + self.script.move_to_position = AsyncMock() + # Mock checkpoint to prevent actual calls + self.script.checkpoint = AsyncMock() + + await self.script.run_block() + + # Calculate expected number of diamond sequences and positions + total_sequences = len(self.script.diamond_sequences) + expected_total_positions = sum( + len(seq["positions"]) for seq in self.script.diamond_sequences + ) + + # Verify checkpoint messages + assert self.script.checkpoint.call_count == total_sequences + + # Verify move_to_position calls + assert ( + self.script.move_to_position.await_count == expected_total_positions + ) + + # Optionally, check the specific calls + expected_calls = [] + for sequence in self.script.diamond_sequences: + for position in sequence["positions"]: + expected_calls.append( + unittest.mock.call(position[0], position[1]) + ) + self.script.move_to_position.assert_has_awaits(expected_calls) + + # Reset mocks for the next iteration + self.script.move_to_position.reset_mock() + self.script.checkpoint.reset_mock() async def test_executable(self): scripts_dir = externalscripts.get_scripts_dir() From ae148113a1fcc16eb52cfae7ad34d6617aabe271 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 22 Nov 2024 14:59:30 -0300 Subject: [PATCH 09/17] Add direction property for the diamond sequence --- .../maintel/tma/move_p2p_diamond.py | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py index fe96eea5b..b7a502860 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py +++ b/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py @@ -51,6 +51,11 @@ class MoveP2PDiamond(BaseBlockScript): - Grid Selection: Choose `grid_az` and `grid_el` values within the telescope's operational limits. Ensure that cumulative movements from the diamond pattern do not exceed these limits. + - Direction Control: Use the `direction` parameter to specify the initial + movement direction of the diamond pattern. This is particularly useful + when starting near operational limits (e.g., high elevations), as it + allows you to avoid exceeding those limits by moving in the opposite + direction. - Understanding the Pattern: The diamond pattern consists of cumulative offsets applied to the initial position. Familiarity with the pattern helps in anticipating telescope movements. @@ -63,6 +68,7 @@ def __init__(self, index: int) -> None: self.grid = dict() self.pause_for = 0.0 self.move_timeout = 120.0 + self.direction = "forward" # Default direction self.LONG_SLEW_AZ = 24.0 # degrees self.LONG_SLEW_EL = 12.0 # degrees @@ -121,6 +127,17 @@ def get_schema(cls): items: type: number minItems: 1 + direction: + description: > + Direction in which to start the diamond pattern movements. + Options are 'forward' or 'backward'. In 'forward' mode, the pattern + starts with positive offsets; in 'backward' mode, it starts with negative offsets. + Default is 'forward'. + type: string + enum: + - forward + - backward + default: forward pause_for: description: Pause duration between movements in seconds. type: number @@ -154,6 +171,7 @@ async def configure(self, config): self.grid["azel"] = dict(az=grid_az, el=grid_el) self.pause_for = config.pause_for self.move_timeout = config.move_timeout + self.direction = config.direction # Read the direction property # Generate and validate all positions self.generate_and_validate_positions() @@ -192,10 +210,16 @@ def generate_diamond_pattern(self, az0, el0): - Movements include long and short slews in azimuth and elevation, as well as diagonal movements. - The sequence is designed to test the telescope's dynamic performance. + - The `direction` parameter controls whether the pattern starts + with positive or negative offsets. Notes: - The diamond pattern created here aims to reproduce the pattern used - for dynamic tests done under BLOCK-T227, T293 abd T294 + - When `direction` is set to `'backward'`, all movement offsets are + reversed, allowing the pattern to start in the opposite direction. + - This is useful for avoiding telescope limits when starting near the + operational boundaries. + - The diamond pattern created here aims to reproduce the pattern used + for dynamic tests done under BLOCK-T227, T293 abd T294 """ # Define the slew offsets for the diamond pattern to match dynamic @@ -220,6 +244,12 @@ def generate_diamond_pattern(self, az0, el0): (+self.SHORT_SLEW_AZ / (2**0.5), -self.SHORT_SLEW_EL / (2**0.5)), ] + # Adjust offsets based on the specified direction + if self.direction == "backward": + azel_slew_offsets = [ + (-az_offset, -el_offset) for az_offset, el_offset in azel_slew_offsets + ] + az = az0 el = el0 positions = [] @@ -260,7 +290,6 @@ def generate_and_validate_positions(self): async def move_to_position(self, az, el): """Move the telescope to the specified azimuth and elevation.""" - self.log.debug(f"Moving telescope to az={az}, el={el}.") await self.mtcs.move_p2p_azel(az=az, el=el, timeout=self.move_timeout) async def run_block(self): From e5f5ab8f86cccad4e07a6fcfe44d009c46736aee Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 29 Nov 2024 14:14:20 -0300 Subject: [PATCH 10/17] Change script name and fix sequence. --- ...ove_p2p_diamond.py => short_long_slews.py} | 4 +- ...ove_p2p_diamond.py => short_long_slews.py} | 73 ++++++++++++------- 2 files changed, 47 insertions(+), 30 deletions(-) rename python/lsst/ts/externalscripts/data/scripts/maintel/tma/{move_p2p_diamond.py => short_long_slews.py} (90%) rename python/lsst/ts/externalscripts/maintel/tma/{move_p2p_diamond.py => short_long_slews.py} (86%) diff --git a/python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/data/scripts/maintel/tma/short_long_slews.py similarity index 90% rename from python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py rename to python/lsst/ts/externalscripts/data/scripts/maintel/tma/short_long_slews.py index 30b911cdc..9792330c9 100755 --- a/python/lsst/ts/externalscripts/data/scripts/maintel/tma/move_p2p_diamond.py +++ b/python/lsst/ts/externalscripts/data/scripts/maintel/tma/short_long_slews.py @@ -21,6 +21,6 @@ import asyncio -from lsst.ts.externalscripts.maintel.tma import MoveP2PDiamond +from lsst.ts.externalscripts.maintel.tma import ShortLongSlews -asyncio.run(MoveP2PDiamond.amain()) +asyncio.run(ShortLongSlews.amain()) diff --git a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py similarity index 86% rename from python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py rename to python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py index b7a502860..a0886f41b 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/move_p2p_diamond.py +++ b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py @@ -18,7 +18,7 @@ # # You should have received a copy of the GNU General Public License -__all__ = ["MoveP2PDiamond"] +__all__ = ["ShortLongSlews"] import asyncio @@ -29,16 +29,17 @@ from lsst.ts.standardscripts.base_block_script import BaseBlockScript -class MoveP2PDiamond(BaseBlockScript): - """Moves the telescope in a diamond pattern around each grid position. +class ShortLongSlews(BaseBlockScript): + """Execute short and long slews in a diamond pattern for a grid of + azimuth and elevation. Overview: This script performs a series of point-to-point (P2P) telescope movements forming a diamond pattern around each user-defined grid - position. It is designed for dynamic tests and performance evaluations, - reproducing patterns used in specific engineering tasks (e.g., BLOCK-T227, - T293, T294). + position with short and long slews. It is designed for dynamic + tests and performance evaluations, reproducing patterns used in + specific engineering tasks (e.g., BLOCK-T293 and T294). Execution Order: @@ -70,10 +71,14 @@ def __init__(self, index: int) -> None: self.move_timeout = 120.0 self.direction = "forward" # Default direction - self.LONG_SLEW_AZ = 24.0 # degrees - self.LONG_SLEW_EL = 12.0 # degrees - self.SHORT_SLEW_AZ = 3.5 # degrees - self.SHORT_SLEW_EL = 3.5 # degrees + # Slews definitions + self.AZ_LONG_SLEW = 24 # deg + self.AZ_SHORT_SLEW = 3.5 # deg + self.EL_LONG_SLEW = 12 # deg + self.EL_SHORT_SLEW = 3.5 # deg + + self.EL_DIAG = 0.5 + self.AZ_DIAG = (1 - self.EL_DIAG**2) ** 0.5 # Telescope limits self.max_el = 86.5 @@ -219,29 +224,41 @@ def generate_diamond_pattern(self, az0, el0): - This is useful for avoiding telescope limits when starting near the operational boundaries. - The diamond pattern created here aims to reproduce the pattern used - for dynamic tests done under BLOCK-T227, T293 abd T294 + for dynamic tests done under T293 abd T294 """ # Define the slew offsets for the diamond pattern to match dynamic - # tests done under BLOCK-T227, T293, T294 + # tests done under T293, T294 azel_slew_offsets = [ (0, 0), - (0, +self.LONG_SLEW_EL), - (0, -self.LONG_SLEW_EL), - (+self.LONG_SLEW_AZ, 0), - (-self.LONG_SLEW_AZ, 0), - (0, +self.SHORT_SLEW_EL), - (0, -self.SHORT_SLEW_EL), - (+self.SHORT_SLEW_AZ, 0), - (-self.SHORT_SLEW_AZ, 0), - (+self.LONG_SLEW_AZ / 2 / (2**0.5), +self.LONG_SLEW_EL / (2**0.5)), - (-self.LONG_SLEW_AZ / 2 / (2**0.5), +self.LONG_SLEW_EL / (2**0.5)), - (-self.LONG_SLEW_AZ / 2 / (2**0.5), -self.LONG_SLEW_EL / (2**0.5)), - (+self.LONG_SLEW_AZ / 2 / (2**0.5), -self.LONG_SLEW_EL / (2**0.5)), - (+self.SHORT_SLEW_AZ / (2**0.5), +self.SHORT_SLEW_EL / (2**0.5)), - (-self.SHORT_SLEW_AZ / (2**0.5), +self.SHORT_SLEW_EL / (2**0.5)), - (-self.SHORT_SLEW_AZ / (2**0.5), -self.SHORT_SLEW_EL / (2**0.5)), - (+self.SHORT_SLEW_AZ / (2**0.5), -self.SHORT_SLEW_EL / (2**0.5)), + (0, +self.EL_LONG_SLEW), + (0, -self.EL_LONG_SLEW), + (+self.AZ_LONG_SLEW, 0), + (-self.AZ_LONG_SLEW, 0), + (0, +self.EL_SHORT_SLEW), + (0, -self.EL_SHORT_SLEW), + (+self.AZ_SHORT_SLEW, 0), + (-self.self.AZ_SHORT_SLEW, 0), + ( + +self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, + +self.EL_LONG_SLEW * self.EL_DIAG, + ), + ( + -self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, + +self.EL_LONG_SLEW * self.EL_DIAG, + ), + ( + -self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, + -self.EL_LONG_SLEW * self.EL_DIAG, + ), + ( + +self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, + -self.EL_LONG_SLEW * self.EL_DIAG, + ), + (+self.AZ_SHORT_SLEW * self.AZ_DIAG, +self.EL_SHORT_SLEW * self.EL_DIAG), + (-self.AZ_SHORT_SLEW * self.AZ_DIAG, +self.EL_SHORT_SLEW * self.EL_DIAG), + (-self.AZ_SHORT_SLEW * self.AZ_DIAG, -self.EL_SHORT_SLEW * self.EL_DIAG), + (+self.AZ_SHORT_SLEW * self.AZ_DIAG, -self.EL_SHORT_SLEW * self.EL_DIAG), ] # Adjust offsets based on the specified direction From 198dd7ad57d505daaf7740296f364b8a027fe4d3 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 29 Nov 2024 14:14:41 -0300 Subject: [PATCH 11/17] Update unit test name. --- .../{test_move_p2p_diamond.py => test_short_long_slews.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/maintel/tma/{test_move_p2p_diamond.py => test_short_long_slews.py} (98%) diff --git a/tests/maintel/tma/test_move_p2p_diamond.py b/tests/maintel/tma/test_short_long_slews.py similarity index 98% rename from tests/maintel/tma/test_move_p2p_diamond.py rename to tests/maintel/tma/test_short_long_slews.py index 0dafe8b01..7c7331629 100644 --- a/tests/maintel/tma/test_move_p2p_diamond.py +++ b/tests/maintel/tma/test_short_long_slews.py @@ -25,14 +25,14 @@ import pytest from lsst.ts import externalscripts, salobj, standardscripts -from lsst.ts.externalscripts.maintel.tma import MoveP2PDiamond +from lsst.ts.externalscripts.maintel.tma import ShortLongSlews -class TestMoveP2PDiamond( +class TestShortLongSlews( standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase ): async def basic_make_script(self, index): - self.script = MoveP2PDiamond(index=index) + self.script = ShortLongSlews(index=index) return (self.script,) @contextlib.asynccontextmanager From 70a88b6b2f344104ac7eaf6fcd1dbe789309ce97 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 29 Nov 2024 15:51:12 -0300 Subject: [PATCH 12/17] Decrease el limiot from 86.5 to 86.0 --- python/lsst/ts/externalscripts/maintel/tma/__init__.py | 2 +- python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/__init__.py b/python/lsst/ts/externalscripts/maintel/tma/__init__.py index cafb015bf..3883e4906 100644 --- a/python/lsst/ts/externalscripts/maintel/tma/__init__.py +++ b/python/lsst/ts/externalscripts/maintel/tma/__init__.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .move_p2p_diamond import * from .random_walk import * from .random_walk_and_take_image_gencam import * from .serpent_walk import * +from .short_long_slews import * diff --git a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py index a0886f41b..968e9655d 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py +++ b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py @@ -81,7 +81,7 @@ def __init__(self, index: int) -> None: self.AZ_DIAG = (1 - self.EL_DIAG**2) ** 0.5 # Telescope limits - self.max_el = 86.5 + self.max_el = 86.0 self.min_el = 15 self.max_az = 180 self.min_az = -180 From dcd64af63d13878b743b91f9af1fd7b503d63d11 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 29 Nov 2024 20:07:11 -0300 Subject: [PATCH 13/17] Fix typo --- .../maintel/tma/short_long_slews.py | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py index 968e9655d..85c55e58b 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py +++ b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py @@ -238,23 +238,11 @@ def generate_diamond_pattern(self, az0, el0): (0, +self.EL_SHORT_SLEW), (0, -self.EL_SHORT_SLEW), (+self.AZ_SHORT_SLEW, 0), - (-self.self.AZ_SHORT_SLEW, 0), - ( - +self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, - +self.EL_LONG_SLEW * self.EL_DIAG, - ), - ( - -self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, - +self.EL_LONG_SLEW * self.EL_DIAG, - ), - ( - -self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, - -self.EL_LONG_SLEW * self.EL_DIAG, - ), - ( - +self.self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, - -self.EL_LONG_SLEW * self.EL_DIAG, - ), + (-self.AZ_SHORT_SLEW, 0), + (+self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, +self.EL_LONG_SLEW * self.EL_DIAG), + (-self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, +self.EL_LONG_SLEW * self.EL_DIAG), + (-self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, -self.EL_LONG_SLEW * self.EL_DIAG), + (+self.AZ_LONG_SLEW / 2 * self.AZ_DIAG, -self.EL_LONG_SLEW * self.EL_DIAG), (+self.AZ_SHORT_SLEW * self.AZ_DIAG, +self.EL_SHORT_SLEW * self.EL_DIAG), (-self.AZ_SHORT_SLEW * self.AZ_DIAG, +self.EL_SHORT_SLEW * self.EL_DIAG), (-self.AZ_SHORT_SLEW * self.AZ_DIAG, -self.EL_SHORT_SLEW * self.EL_DIAG), From a0827a334f6f3a6213cf635310e0483ba83327fc Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Fri, 29 Nov 2024 20:42:42 -0300 Subject: [PATCH 14/17] Fix executable filename in unit test. --- tests/maintel/tma/test_short_long_slews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/maintel/tma/test_short_long_slews.py b/tests/maintel/tma/test_short_long_slews.py index 7c7331629..90fa1b29d 100644 --- a/tests/maintel/tma/test_short_long_slews.py +++ b/tests/maintel/tma/test_short_long_slews.py @@ -226,5 +226,5 @@ async def test_run_block(self): async def test_executable(self): scripts_dir = externalscripts.get_scripts_dir() - script_path = scripts_dir / "maintel" / "tma" / "move_p2p_diamond.py" + script_path = scripts_dir / "maintel" / "tma" / "short_long_slews.py" await self.check_executable(script_path) From 6655b85e1234ca35d1fec25f242120e5514c654a Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Sat, 30 Nov 2024 18:40:48 -0300 Subject: [PATCH 15/17] Fix schema description --- .../ts/externalscripts/maintel/tma/short_long_slews.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py index 85c55e58b..4682700d6 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py +++ b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py @@ -109,7 +109,7 @@ def get_schema(cls): type: object properties: grid_az: - description: > + description: >- Azimuth coordinate(s) in degrees where the diamond patterns will be executed. Can be a single number or a list of numbers. Ensure that the azimuth values, along with the cumulative offsets from the diamond pattern, remain within @@ -121,7 +121,7 @@ def get_schema(cls): type: number minItems: 1 grid_el: - description: > + description: >- Elevation coordinate(s) in degrees where the diamond patterns will be executed. Can be a single number or a list of numbers. Ensure that the elevation values, along with the cumulative offsets from the diamond pattern, remain within @@ -133,11 +133,11 @@ def get_schema(cls): type: number minItems: 1 direction: - description: > + description: >- Direction in which to start the diamond pattern movements. Options are 'forward' or 'backward'. In 'forward' mode, the pattern - starts with positive offsets; in 'backward' mode, it starts with negative offsets. - Default is 'forward'. + starts with positive offsets; in 'backward' mode, it starts with negative + offsets. Default is 'forward'. type: string enum: - forward From 32c38d4f1d2ed22cbea5ce816e48b166d8eddd0f Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Sat, 30 Nov 2024 19:05:14 -0300 Subject: [PATCH 16/17] Update doc string and checkpoint message --- .../maintel/tma/short_long_slews.py | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py index 4682700d6..3e804d787 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py +++ b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py @@ -30,41 +30,36 @@ class ShortLongSlews(BaseBlockScript): - """Execute short and long slews in a diamond pattern for a grid of - azimuth and elevation. + """Execute short and long slews for a grid of azimuths and elevations. Overview: This script performs a series of point-to-point (P2P) telescope movements forming a diamond pattern around each user-defined grid - position with short and long slews. It is designed for dynamic - tests and performance evaluations, reproducing patterns used in - specific engineering tasks (e.g., BLOCK-T293 and T294). + position with short and long slews. It is designed for dynamic tests + and performance evaluations, reproducing patterns used in specific + engineering tasks (e.g., BLOCK-T293 and T294). Execution Order: The script processes the grid positions by iterating over `grid_az` first, then `grid_el`. For each azimuth value in `grid_az`, it executes - the diamond patterns at all elevation values in `grid_el` in order. + short and long slews at all elevation values in the `grid_el` list. User Guidance: - Grid Selection: Choose `grid_az` and `grid_el` values within the telescope's operational limits. Ensure that cumulative movements - from the diamond pattern do not exceed these limits. + from the slews do not exceed these limits. - Direction Control: Use the `direction` parameter to specify the initial - movement direction of the diamond pattern. This is particularly useful - when starting near operational limits (e.g., high elevations), as it - allows you to avoid exceeding those limits by moving in the opposite - direction. - - Understanding the Pattern: The diamond pattern consists of cumulative - offsets applied to the initial position. Familiarity with the pattern - helps in anticipating telescope movements. + movement direction of the slew. This is particularly useful when starting + near operational limits (e.g., high elevations), as it allows you to + avoid exceeding those limits by moving in the opposite direction. - Operational Limits: Azimuth: -180° to +180°, Elevation: 15° to 86.5°. """ def __init__(self, index: int) -> None: - super().__init__(index, descr="Move telescope in diamond pattern around grid.") + super().__init__(index, descr="Execute a sequence of short and long slews.") self.mtcs = None self.grid = dict() self.pause_for = 0.0 @@ -105,15 +100,15 @@ def get_schema(cls): # You can retain or update the schema as before schema_yaml = """ $schema: http://json-schema.org/draft-07/schema# - title: MoveDiamondPattern Configuration + title: ShortLongSlews Configuration type: object properties: grid_az: description: >- - Azimuth coordinate(s) in degrees where the diamond patterns will be executed. + Azimuth coordinate(s) in degrees where the short and long slews will be executed. Can be a single number or a list of numbers. Ensure that the azimuth values, - along with the cumulative offsets from the diamond pattern, remain within - the telescope's operational limits. + along with the cumulative offsets from the slews, remain within the telescope's + operational limits. anyOf: - type: number - type: array @@ -122,10 +117,10 @@ def get_schema(cls): minItems: 1 grid_el: description: >- - Elevation coordinate(s) in degrees where the diamond patterns will be executed. + Elevation coordinate(s) in degrees where the short and long slews will be executed. Can be a single number or a list of numbers. Ensure that the elevation values, - along with the cumulative offsets from the diamond pattern, remain within - the telescope's operational limits + along with the cumulative offsets from the slews, remain within the telescope's + operational limits. anyOf: - type: number - type: array @@ -134,10 +129,9 @@ def get_schema(cls): minItems: 1 direction: description: >- - Direction in which to start the diamond pattern movements. - Options are 'forward' or 'backward'. In 'forward' mode, the pattern - starts with positive offsets; in 'backward' mode, it starts with negative - offsets. Default is 'forward'. + Direction in which to start the slews. Options are 'forward' or 'backward'. + In 'forward' mode, the pattern starts with positive offsets; in 'backward' + mode, it starts with negative offsets. Default is 'forward'. type: string enum: - forward @@ -199,7 +193,8 @@ def set_metadata(self, metadata: type_hints.BaseMsgType) -> None: def generate_diamond_pattern(self, az0, el0): """ - Generate a diamond pattern of azimuth and elevation coordinates. + Generate a diamond pattern of azimuth and elevation coordinates + with short and long slews. Parameters: az0 (float): Initial azimuth coordinate. @@ -207,7 +202,7 @@ def generate_diamond_pattern(self, az0, el0): Returns: - `positions` (list of tuple): List of positions forming - the diamond patterns. + a kind of a diamond pattern with short and long slews. Pattern Details: - The pattern consists of cumulative movements starting from the @@ -306,12 +301,13 @@ async def run_block(self): positions = sequence["positions"] # Output checkpoint message await self.checkpoint( - f"Starting diamond sequence {i+1}/{total_diamonds} at grid point (Az={az0}, El={el0})" + f"Starting sequence (short and long slews) {i+1}/{total_diamonds} " + f"at grid point (Az={az0}, El={el0})" ) total_positions = len(positions) for j, (az, el) in enumerate(positions, start=1): self.log.info( - f"Moving to position {j}/{total_positions} in diamond sequence {i+1}: Az={az}, El={el}" + f"Moving to position {j}/{total_positions} of grid sequence {i+1}: Az={az}, El={el}" ) await self.move_to_position(az, el) self.log.info(f"Pausing for {self.pause_for}s.") From 06451bff251f97c3135f42fbc90551d08d2432f2 Mon Sep 17 00:00:00 2001 From: David Sanmartim Date: Sat, 30 Nov 2024 19:10:12 -0300 Subject: [PATCH 17/17] Update checkpoint message again --- python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py index 3e804d787..cf41d0e4b 100755 --- a/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py +++ b/python/lsst/ts/externalscripts/maintel/tma/short_long_slews.py @@ -301,8 +301,7 @@ async def run_block(self): positions = sequence["positions"] # Output checkpoint message await self.checkpoint( - f"Starting sequence (short and long slews) {i+1}/{total_diamonds} " - f"at grid point (Az={az0}, El={el0})" + f"Starting sequence {i+1}/{total_diamonds} at grid point (Az={az0}, El={el0})" ) total_positions = len(positions) for j, (az, el) in enumerate(positions, start=1):