#
# ISC License
#
# Copyright (c) 2025, Autonomous Vehicle Systems
# Laboratory, University of Colorado Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import pytest
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import orbitalMotion
from Basilisk.architecture import messaging
from Basilisk.fswAlgorithms import orbElemOffset
[docs]
@pytest.mark.parametrize("useMeanAnomalyOffset", [False, True])
def test_orbElemOffset(useMeanAnomalyOffset):
"""
Functional test for OrbElemOffset:
- When useMeanAnomalyOffset is False:
All ClassicElements fields are summed element-wise:
out = main + offset
- When useMeanAnomalyOffset is True:
All fields except f are still summed element-wise. The f field
is computed by interpreting offset.f as a mean anomaly increment
DeltaM (rad) and performing
E_main = f2E(main.f, main.e)
M_main = E2M(E_main, main.e)
e_out = main.e + offset.e
M_out = M_main + offset.f
f_out = E2f(M2E(M_out, e_out), e_out)
"""
# 1. Create test simulation
scSim = SimulationBaseClass.SimBaseClass()
proc = scSim.CreateNewProcess("TestProc")
task = scSim.CreateNewTask("unitTask", macros.sec2nano(0.1))
proc.addTask(task)
# 2. Construct the module
module = orbElemOffset.OrbElemOffset()
module.ModelTag = "OrbElemOffset"
module.useMeanAnomalyOffset = useMeanAnomalyOffset
scSim.AddModelToTask("unitTask", module)
# 3. Create two ClassicElements messages
main = messaging.ClassicElementsMsgPayload()
offset = messaging.ClassicElementsMsgPayload()
# Main elements (choose a moderately eccentric orbit)
main.a = 1.0 # [m]
main.e = 0.1 # [-]
main.i = 0.2 # [rad]
main.Omega = 0.3 # [rad]
main.omega = 0.4 # [rad]
main.f = 0.5 # [rad]
# Offsets: small increments
offset.a = 2.0 # [m]
offset.e = 0.02 # [-]
offset.i = 0.03 # [rad]
offset.Omega = 0.04 # [rad]
offset.omega = 0.05 # [rad]
# For the angle offset, this will be interpreted either as df or DeltaM
offset.f = 0.06 # [rad]
msgMain = messaging.ClassicElementsMsg().write(main)
msgOffset = messaging.ClassicElementsMsg().write(offset)
# 4. Subscribe module input messages
module.mainElementsInMsg.subscribeTo(msgMain)
module.offsetElementsInMsg.subscribeTo(msgOffset)
# 5. Record output
recorder = module.elementsOutMsg.recorder()
scSim.AddModelToTask("unitTask", recorder)
# Initialize and run one step
scSim.InitializeSimulation()
scSim.ConfigureStopTime(macros.sec2nano(0.1))
scSim.ExecuteSimulation()
# 6. Extract result - only one write should have occurred
assert recorder.a.shape[0] > 0
idx = 0
# Common expected fields: always simple sums
expected_a = main.a + offset.a
expected_e = main.e + offset.e
expected_i = main.i + offset.i
expected_Omega = main.Omega + offset.Omega
expected_omega = main.omega + offset.omega
# Check non angle fields
assert recorder.a[idx] == pytest.approx(expected_a)
assert recorder.e[idx] == pytest.approx(expected_e)
assert recorder.i[idx] == pytest.approx(expected_i)
assert recorder.Omega[idx] == pytest.approx(expected_Omega)
assert recorder.omega[idx] == pytest.approx(expected_omega)
# 7. Check the anomaly handling
if not useMeanAnomalyOffset:
# Plain true anomaly increment
expected_f = main.f + offset.f
assert recorder.f[idx] == pytest.approx(expected_f)
else:
recorderM = orbitalMotion.E2M(orbitalMotion.f2E(recorder.f[idx], recorder.e[idx]), recorder.e[idx])
mainM = orbitalMotion.E2M(orbitalMotion.f2E(main.f, main.e), main.e)
assert (recorderM - mainM) == pytest.approx(offset.f, rel=1e-12, abs=1e-12)
if __name__ == "__main__":
# Run the test standalone for quick checking
test_orbElemOffset(useMeanAnomalyOffset=False)
test_orbElemOffset(useMeanAnomalyOffset=True)