Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command events to implement maneuver to pitch and roll about sun line #315

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions kadi/commands/command_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ def cmd_set_nsm(date=None):
return out


def cmd_set_maneuver_sun_pitch(pitch, date=None):
"""Maneuver to ``pitch`` Sun pitch angle (absolute)."""
cmd = dict(type="LOAD_EVENT", tlmsid="SUN_PITCH", params={"PITCH": pitch})
return (cmd,)


def cmd_set_maneuver_sun_rasl(rasl, date=None):
"""Maneuver by ``angle`` degrees in roll about Sun line (relative to current)."""
cmd = dict(type="LOAD_EVENT", tlmsid="SUN_RASL", params={"RASL": rasl})
return (cmd,)


def cmd_set_safe_mode(date=None):
safe_mode_cmds = (
dict(type="COMMAND_SW", tlmsid="ACPCSFSU"), # CPE set pcad mode to safe sun
Expand Down
89 changes: 88 additions & 1 deletion kadi/commands/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from astropy.table import Column, Table
from chandra_time import DateTime, date2secs, secs2date
from cxotime import CxoTime
from Quaternion import quat_to_equatorial
from Quaternion import Quat, quat_to_equatorial

from kadi import commands

Expand Down Expand Up @@ -1394,6 +1394,93 @@ def callback(cls, date, transitions, state, idx):
cls.add_manvr_transitions(date, transitions, state, idx)


class ManeuverSunPitchTransition(ManeuverTransition):
"""
Like ``ManeuverTransition`` except perform a pure-pitch maneuver from attitude.

This does not change the PCAD mode.
"""

command_attributes = {"type": "LOAD_EVENT", "tlmsid": "SUN_PITCH"}
state_keys = PCAD_STATE_KEYS

@classmethod
def set_transitions(cls, transitions, cmds, start, stop):
state_cmds = cls.get_state_changing_commands(cmds)

for cmd in state_cmds:
transitions[cmd["date"]]["maneuver_transition"] = functools.partial(
cls.callback, cmd["params"]["pitch"]
)

@staticmethod
def callback(pitch, date, transitions, state, idx):
# Setup for maneuver to sun-pointed attitude from current att
curr_att = [state[qc] for qc in QUAT_COMPS]

# If current attitude is not defined then just drop the NSM maneuver on
# the floor. The state will start getting defined when the first normal
# maneuver happens.
if None in curr_att:
return

curr_att = Quat(curr_att)
sun_ra, sun_dec = ska_sun.position(date)
curr_pitch = ska_sun.pitch(
curr_att.ra, curr_att.dec, sun_ra=sun_ra, sun_dec=sun_dec
)
targ_att = ska_sun.apply_sun_pitch_yaw(
curr_att, pitch=pitch - curr_pitch, yaw=0, sun_ra=sun_ra, sun_dec=sun_dec
)
for qc, targ_q in zip(QUAT_COMPS, targ_att.q):
state["targ_" + qc] = targ_q

# Do the maneuver
ManeuverTransition.add_manvr_transitions(date, transitions, state, idx)


class ManeuverSunRaslTransition(ManeuverTransition):
"""
Like ``ManeuverTransition`` except roll about the sun line.

This does not change the PCAD mode.
"""

command_attributes = {"type": "LOAD_EVENT", "tlmsid": "SUN_RASL"}
state_keys = PCAD_STATE_KEYS

@classmethod
def set_transitions(cls, transitions, cmds, start, stop):
state_cmds = cls.get_state_changing_commands(cmds)

for cmd in state_cmds:
transitions[cmd["date"]]["maneuver_transition"] = functools.partial(
cls.callback, cmd["params"]["rasl"]
)

@staticmethod
def callback(rasl, date, transitions, state, idx):
# Setup for maneuver to sun-pointed attitude from current att
curr_att = [state[qc] for qc in QUAT_COMPS]

# If current attitude is not defined then just drop the NSM maneuver on
# the floor. The state will start getting defined when the first normal
# maneuver happens.
if None in curr_att:
return

curr_att = Quat(curr_att)
sun_ra, sun_dec = ska_sun.position(date)
targ_att = ska_sun.apply_sun_pitch_yaw(
jeanconn marked this conversation as resolved.
Show resolved Hide resolved
curr_att, yaw=rasl, sun_ra=sun_ra, sun_dec=sun_dec
)
for qc, targ_q in zip(QUAT_COMPS, targ_att.q):
state["targ_" + qc] = targ_q

# Do the maneuver
ManeuverTransition.add_manvr_transitions(date, transitions, state, idx)


###################################################################
# ACIS transitions
###################################################################
Expand Down
88 changes: 88 additions & 0 deletions kadi/commands/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import parse_cm.paths
import parse_cm.tests
import pytest
import ska_sun
from astropy.table import Table, vstack
from chandra_time import secs2date
from cxotime import CxoTime
Expand All @@ -21,6 +22,7 @@
message="kadi commands v1 is deprecated, use v2 instead",
)

import kadi.commands.states as kcs
from kadi import commands
from kadi.commands import (
commands_v1,
Expand Down Expand Up @@ -556,6 +558,92 @@ def test_cmds_scenario(stop_date_2020_12_03): # noqa: ARG001
commands_v2.clear_caches()


stop_date_2024_01_30 = stop_date_fixture_factory("2024-01-30")


@pytest.mark.skipif(not HAS_INTERNET, reason="No internet connection")
def test_nsm_offset_pitch_rasl_command_events(stop_date_2024_01_30): # noqa: ARG001
"""Test custom scenario with NSM offset pitch load event command"""
# First make the cmd_events.csv file for the scenario
scenario = "test_nsm_offset_pitch"
cmds_dir = Path(commands_v2.conf.commands_dir) / scenario
cmds_dir.mkdir(exist_ok=True, parents=True)
# Note variation in format of date, since this comes from humans.
cmd_evts_text = """\
State,Date,Event,Params,Author,Reviewer,Comment
Definitive,2024:025:04:00:00,Maneuver sun rasl,90,,,
Definitive,2024:025:00:00:00,Maneuver sun pitch,160,,,
Definitive,2024:024:09:44:06,NSM,,,,
"""
(cmds_dir / "cmd_events.csv").write_text(cmd_evts_text)

# Now get commands in a time range that includes the new command events
cmds = commands_v2.get_cmds(
"2024-01-24 12:00:00", "2024-01-25 05:00:00", scenario=scenario
)
cmds = cmds[(cmds["tlmsid"] != "OBS") & (cmds["type"] != "ORBPOINT")]
exp = [
"2024:025:00:00:00.000 | LOAD_EVENT | SUN_PITCH | CMD_EVT | event=Maneuver_sun_pitch, event_date=2024:025:00:00:00, pitch=160, scs=0",
"2024:025:04:00:00.000 | LOAD_EVENT | SUN_RASL | CMD_EVT | event=Maneuver_sun_rasl, event_date=2024:025:04:00:00, rasl=90, scs=0",
]

assert cmds.pformat_like_backstop() == exp

states = kcs.get_states(
"2024:024:09:00:00",
"2024:025:02:00:00",
state_keys=["pitch", "pcad_mode"],
scenario=scenario,
)
exp = [
" datestart pitch pcad_mode",
"--------------------- ----- ---------",
"2024:024:09:00:00.000 172.7 NPNT",
"2024:024:09:13:49.112 172.7 NMAN",
"2024:024:09:13:59.363 170.9 NMAN",
"2024:024:09:18:48.003 162.4 NMAN",
"2024:024:09:23:36.644 145.8 NMAN",
"2024:024:09:28:25.284 126.1 NMAN",
"2024:024:09:33:13.925 109.8 NMAN",
"2024:024:09:38:02.565 101.5 NMAN",
"2024:024:09:42:51.205 99.6 NPNT",
"2024:024:09:44:06.000 97.2 NSUN",
"2024:024:09:49:20.973 92.4 NSUN",
"2024:024:09:54:35.946 90.0 NSUN",
"2024:025:00:00:00.000 93.0 NSUN",
"2024:025:00:05:17.540 104.5 NSUN",
"2024:025:00:10:35.081 125.2 NSUN",
"2024:025:00:15:52.621 146.0 NSUN",
"2024:025:00:21:10.161 157.4 NSUN",
"2024:025:00:26:27.701 160.0 NSUN",
]

out = states["datestart", "pitch", "pcad_mode"]
out["pitch"].format = ".1f"
assert out.pformat_all() == exp

states = kcs.get_states(
"2024:024:09:00:00",
"2024:025:08:00:00",
state_keys=["q1", "q2", "q3", "q4"],
scenario=scenario,
)

# Interpolate states at two times just after the pitch maneuver and just after the
# roll about sun line (rasl) maneuver.
dates = ["2024:025:00:30:00", "2024:025:05:00:00"]
sts = kcs.interpolate_states(states, dates)
q1 = Quat([sts["q1"][0], sts["q2"][0], sts["q3"][0], sts["q4"][0]])
q2 = Quat([sts["q1"][1], sts["q2"][1], sts["q3"][1], sts["q4"][1]])
pitch1, rasl1 = ska_sun.get_sun_pitch_yaw(q1.ra, q1.dec, dates[0])
pitch2, rasl2 = ska_sun.get_sun_pitch_yaw(q2.ra, q2.dec, dates[1])
assert np.isclose(pitch1, 160, atol=0.1)
assert np.isclose(pitch2, 160, atol=0.5)
assert np.isclose((rasl2 - rasl1) % 360, 90, atol=0.5)

commands_v2.clear_caches()


def test_command_set_bsh():
cmds = get_cmds_from_event("2000:001", "Bright star hold", "")
exp = """\
Expand Down