Source code for test_simpleAntenna

#
#  ISC License
#
#  Copyright (c) 2025, Autonomous Vehicle Systems Lab, 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 numpy as np
import pytest

from Basilisk import __path__
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import unitTestSupport
from Basilisk.architecture import messaging
from Basilisk.utilities import macros, orbitalMotion
from Basilisk.simulation import simpleAntenna
from Basilisk.utilities import RigidBodyKinematics as rbk
from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile

bskPath = __path__[0]

# Constants
K_BOLTZMANN = 1.38064852e-23
ACCURACY = 1e-6

def earth_horizon_sigma():
    theta = np.arcsin(
        (orbitalMotion.REQ_EARTH * 1e3)
        / ((800 + orbitalMotion.REQ_EARTH) * 1e3)
    )
    return rbk.C2MRP(
        np.array([
            [0, 0, 1],
            [np.sin(theta), np.cos(theta), 0],
            [-np.cos(theta), np.sin(theta), 0],
        ])
    )

[docs] @pytest.mark.parametrize("ACCURACY", [1e-6]) @pytest.mark.parametrize( "antennaState, env_type, T_AmbientSet, useHaslamMap, T_sky_true, eclipseValue, sigma_AB, scPosUnit", [ (simpleAntenna.ANTENNA_OFF, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RX, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_TX, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 1, 150, False, 200, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, None, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 1, None, False, 200, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, True, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 300.0, 0.0, rbk.C2MRP(np.array([[0, 0, 1], [0, 1, 0], [-1, 0, 0]])), [1, 0, 0]), # Case with pointing Earth and sun blocked (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 300.0, 1.0, rbk.C2MRP(np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]])), [0, 1, 0]), # Case with pointing Earth and sun is visible but not in the cone (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 5.660060787285246, 1.0, rbk.C2MRP(np.array([[0, 0, -1], [0, 1, 0], [1, 0, 0]])), [1, 0, 0]), # Case with pointing Moon (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 37.82128898104384, 1.0, rbk.C2MRP(np.array([[0, 0, 1], [0, 1, 0], [-1, 0, 0]])), [-1, 0, 0]), # Case with pointing Sun and sun in the cone (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 147.9662263230431, 0.0, earth_horizon_sigma(), [1, 0, 0]), # Case with pointing Earth with partial coverage ] ) def test_simpleAntenna(antennaState, env_type, T_AmbientSet, useHaslamMap, T_sky_true, eclipseValue, sigma_AB, scPosUnit, ACCURACY): r""" **Validation Test Description** This unit test validates the thermal noise temperature and sky contribution computed by the ``simpleAntenna`` module under a variety of antenna operating modes, environmental configurations, and pointing geometries. The test verifies correct behavior when the antenna is transmitting, receiving, both transmitting and receiving, or turned off, as well as when different celestial bodies (Earth, Sun, Moon) are inside or outside the antenna field of view. The test also exercises eclipse handling, ambient temperature settings, and antenna orientation effects using direction cosine matrices. **Test Parameters** The test is fully parameterized to cover multiple combinations of antenna state, environment type, ambient temperature configuration, and antenna pointing geometry. Args: antennaState (int): Antenna operational mode. One of ``ANTENNA_OFF``, ``ANTENNA_RX``, ``ANTENNA_TX``, or ``ANTENNA_RXTX``. env_type (int): Environment selection flag. This controls where antenna is attached: - 0: Spacecraft environment - 1: Ground station environment T_AmbientSet (float or None): Ambient antenna temperature in Kelvin. If ``None``, the module computes the ambient temperature internally using environmental models. useHaslamMap (bool): Flag indicating whether the Haslam sky map is used to model galactic background noise. T_sky_true (float): Truth value for the expected sky noise temperature (K) used for validation. eclipseValue (float): Eclipse factor in the range [0, 1], where 0 indicates full eclipse (Sun completely blocked) and 1 indicates no eclipse. sigma_AB (array-like or ``Eigen::MRP``): Antenna orientation relative to the spacecraft body frame, expressed as Modified Rodrigues Parameters (MRPs). This defines the antenna boresight direction used to determine which celestial bodies fall within the antenna cone. scPosUnit (array-like, shape (3,)): Unit vector of the spacecraft position expressed in the inertial frame. This is used to define the relative geometry between the spacecraft, Earth, Sun, and Moon. ACCURACY (float): Absolute numerical tolerance used when comparing the computed sky temperature against the truth value. **Description of Variables Being Tested** This test validates: - Correct handling of antenna operational modes (OFF, RX, TX, RXTX) - Ground station vs. spacecraft environment configurations - Proper eclipse handling when the Sun is partially or fully blocked - Correct use of antenna pointing geometry via DCM/MRP transformations - Robust behavior when ambient temperature is user-specified or internally computed - Numerical ACCURACY of sky temperature computation within the specified tolerance """ [testResults, testMessage, _] = simpleAntennaTestFunction(antennaState, env_type, T_AmbientSet, useHaslamMap, T_sky_true, eclipseValue, sigma_AB, scPosUnit, ACCURACY) assert testResults < 1, testMessage
[docs] def test_simpleAntenna_haslamMap_three_runs_and_pointing_difference(): """ Haslam map coverage: Run one sim instance and change pointing while Haslam is enabled. Verify that sky temperature changes with pointing direction. """ # --- Fixed configuration --- antennaState = simpleAntenna.ANTENNA_RXTX env_type = 0 T_AmbientSet = 150.0 eclipseValue = 1.0 scPosUnit = [1, 0, 0] # consistent with other tests frequency = 2.2e9 bandwidth = 5e6 T_E = 50.0 eta_r = 0.6 useHaslamMap = True # Direction A: identity (boresight along +Z in body frame as you use [0,0,1] for antenna position) sigma_AB_A = [0, 0, 0] # Direction B: rotate so body +Z maps to +Y (same matrix you used) sigma_AB_B = rbk.C2MRP(np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]])) # --- 1) Setup ONE simulation instance --- unitTestSim = SimulationBaseClass.SimBaseClass() unitTaskName = "unitTaskHaslam" unitProcessName = "TestProcessHaslam" testProcessRate = macros.sec2nano(0.5) testProc = unitTestSim.CreateNewProcess(unitProcessName) testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate)) module = simpleAntenna.SimpleAntenna() module.ModelTag = "simpleAntennaHaslamTag" module.setAntennaName("simpleAntenna") # --- 2) Messages (same style as your main test) --- # Sun sunMessage = messaging.SpicePlanetStateMsgPayload() sunMessage.PlanetName = "sun" sunMessage.PositionVector = [0, 0, 0] sunMsg = messaging.SpicePlanetStateMsg().write(sunMessage) # Earth earthMessage = messaging.SpicePlanetStateMsgPayload() earthMessage.PlanetName = "earth" earthMessage.PositionVector = [orbitalMotion.AU * 1000, 0, 0] earthMsg = messaging.SpicePlanetStateMsg().write(earthMessage) # Moon moonMessage = messaging.SpicePlanetStateMsgPayload() moonMessage.PlanetName = "moon" moonMessage.PositionVector = [(orbitalMotion.AU + orbitalMotion.SMA_MOON) * 1000, 0, 0] moonMsg = messaging.SpicePlanetStateMsg().write(moonMessage) # Eclipse eclipseMessage = messaging.EclipseMsgPayload() eclipseMessage.illuminationFactor = eclipseValue eclipseMsg = messaging.EclipseMsg().write(eclipseMessage) # Antenna state antennaStateData = messaging.AntennaStateMsgPayload() antennaStateData.antennaState = antennaState antennaStateMsg = messaging.AntennaStateMsg().write(antennaStateData) # Spacecraft state scMessage = messaging.SCStatesMsgPayload() altitude = 800 * 1000 # [m] scPos = np.array(scPosUnit) * (altitude + orbitalMotion.REQ_EARTH * 1e3) scMessage.r_BN_N = [orbitalMotion.AU * 1000, 0, 0] + scPos scMessage.sigma_BN = [0, 0, 0] scMsg = messaging.SCStatesMsg().write(scMessage) # Subscribe module.scStateInMsg.subscribeTo(scMsg) module.antennaSetStateInMsg.subscribeTo(antennaStateMsg) module.sunInMsg.subscribeTo(sunMsg) module.sunEclipseInMsg.subscribeTo(eclipseMsg) module.addPlanetToModel(earthMsg) module.addPlanetToModel(moonMsg) # --- 3) Module parameters --- module.setAntennaFrequency(frequency) module.setAntennaBandwidth(bandwidth) module.setAntennaDirectivity_dB(20.0) module.setAntennaHpbwRatio(1.0) module.setAntennaP_Tx(100.0) module.setAntennaP_Rx(10.0) module.setAntennaEquivalentNoiseTemp(T_E) module.setAntennaRadEfficiency(eta_r) module.setAntennaPositionBodyFrame([0, 0, 1]) module.setAntennaEnvironmentTemperature(T_AmbientSet) # Turn Haslam ON for this test (do not rely on the helper) module.setUseHaslamMap(useHaslamMap) # Add to task + recorder unitTestSim.AddModelToTask(unitTaskName, module) dataLog = module.antennaOutMsg.recorder() unitTestSim.AddModelToTask(unitTaskName, dataLog) def tsky_from_last_log(): #reverse engineer the sky temperature from the noise """Infer sky temperature from last logged noise power.""" p_noise = float(dataLog.P_N[-1]) T_S_sim = p_noise / (K_BOLTZMANN * bandwidth) return (T_S_sim - T_E - (1.0 - eta_r) * T_AmbientSet) / eta_r # --- RUN A: Direction A --- module.setAntennaOrientationBodyFrame(sigma_AB_A) unitTestSim.InitializeSimulation() unitTestSim.ConfigureStopTime(macros.sec2nano(0.5)) unitTestSim.ExecuteSimulation() Tsky_on_A = tsky_from_last_log() # --- RUN B: Direction B (same sim, continue) module.setAntennaOrientationBodyFrame(sigma_AB_B) unitTestSim.ConfigureStopTime(macros.sec2nano(1.0)) unitTestSim.ExecuteSimulation() Tsky_on_B = tsky_from_last_log() # --- RUN C: Direction A again to test repeatability in the same run module.setAntennaOrientationBodyFrame(sigma_AB_A) unitTestSim.ConfigureStopTime(macros.sec2nano(1.5)) unitTestSim.ExecuteSimulation() Tsky_on_A2 = tsky_from_last_log() # --- Prints before asserts (as requested) --- print(f"Tsky_on_A = {Tsky_on_A}") print(f"Tsky_on_B = {Tsky_on_B}") print(f"Tsky_on_A2 = {Tsky_on_A2}") # --- Assertions --- # Haslam should produce sky temps above CMB baseline in most directions assert Tsky_on_A > 2.7 assert Tsky_on_B > 2.7 # Make sure pointing changes the value by a noticeable amount assert abs(Tsky_on_A - Tsky_on_B) > 0.1 # Make sure same inertial pointing as before but different timestep has similar temperature assert abs(Tsky_on_A - Tsky_on_A2) < 0.1
[docs] def simpleAntennaTestFunction(antennaState, env_type, T_AmbientSet, useHaslamMap, T_sky_true, eclipseValue, sigma_AB, scPosUnit, ACCURACY): """Test method""" testFailCount = 0 testMessages = [] unitTaskName = "unitTask" unitProcessName = "TestProcess" unitTestSim = SimulationBaseClass.SimBaseClass() testProcessRate = macros.sec2nano(0.5) testProc = unitTestSim.CreateNewProcess(unitProcessName) testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate)) # Set up module to be tested module = simpleAntenna.SimpleAntenna() module.ModelTag = "simpleAntennaTag" module.setAntennaName("simpleAntenna") unitTestSim.AddModelToTask(unitTaskName, module) # Set up Sun message sunMessage = messaging.SpicePlanetStateMsgPayload() sunMessage.PlanetName = "sun" sunMessage.PositionVector = [0, 0, 0] sunMsg = messaging.SpicePlanetStateMsg().write(sunMessage) # Set up Earth message earthMessage = messaging.SpicePlanetStateMsgPayload() earthMessage.PlanetName = "earth" earthMessage.PositionVector = [orbitalMotion.AU*1000, 0, 0] earthMsg = messaging.SpicePlanetStateMsg().write(earthMessage) # Set up Moon message moonMessage = messaging.SpicePlanetStateMsgPayload() moonMessage.PlanetName = "moon" moonMessage.PositionVector = [(orbitalMotion.AU+orbitalMotion.SMA_MOON)*1000, 0, 0] moonMsg = messaging.SpicePlanetStateMsg().write(moonMessage) # Set up eclipse message eclipseMessage = messaging.EclipseMsgPayload() eclipseMessage.illuminationFactor = eclipseValue # Set it to be totally in shadow eclipseMsg = messaging.EclipseMsg().write(eclipseMessage) # Set up antenna state message (ANTENNA_OFF = 0, ANTENNA_RX = 1, ANTENNA_TX = 2, ANTENNA_RXTX = 3) antennaStateData = messaging.AntennaStateMsgPayload() antennaStateData.antennaState = antennaState antennaStateMsg = messaging.AntennaStateMsg().write(antennaStateData) # subscribe input messages to module if env_type == 0: # space environment # Set up spacecraft message scMessage = messaging.SCStatesMsgPayload() altitude = 800 * 1000 # [m] 800 km altitude scPos = np.array(scPosUnit) * (altitude + orbitalMotion.REQ_EARTH*1e3) scMessage.r_BN_N = [orbitalMotion.AU*1000, 0, 0] + scPos # [m] position of spacecraft in inertial frame scMessage.sigma_BN = [0,0,0] scMsg = messaging.SCStatesMsg().write(scMessage) module.scStateInMsg.subscribeTo(scMsg) elif env_type == 1: # ground station environment # Set up ground station message groundMessage = messaging.GroundStateMsgPayload() groundMessage.r_LN_N = [orbitalMotion.AU *1000, 0, 0] groundMessage.r_LP_N = [orbitalMotion.REQ_EARTH*1000, 0, 0] groundMsg = messaging.GroundStateMsg().write(groundMessage) module.groundStateInMsg.subscribeTo(groundMsg) else: testFailCount += 1 testMessages.append("Error: invalid env_type") module.antennaSetStateInMsg.subscribeTo(antennaStateMsg) module.sunInMsg.subscribeTo(sunMsg) module.addPlanetToModel(earthMsg) module.addPlanetToModel(moonMsg) module.sunEclipseInMsg.subscribeTo(eclipseMsg) # use setters to set module parameters frequency = 2.2e9 # Hz module.setAntennaFrequency(frequency) bandwidth = 5e6 # Hz module.setAntennaBandwidth(bandwidth) directivity = 20.0 # dB module.setAntennaDirectivity_dB(directivity) hpbwRatio = 1.0 # [-] module.setAntennaHpbwRatio(hpbwRatio) P_Tx = 100.0 # W module.setAntennaP_Tx(P_Tx) P_Rx = 10.0 # W module.setAntennaP_Rx(P_Rx) T_E = 50.0 # K module.setAntennaEquivalentNoiseTemp(T_E) eta_r = 0.6 # [-] module.setAntennaRadEfficiency(eta_r) module.setAntennaPositionBodyFrame([0, 0, 1]) # m module.setAntennaOrientationBodyFrame(sigma_AB) module.setUseHaslamMap(useHaslamMap) # Try both true and false if T_AmbientSet is not None: T_Ambient = T_AmbientSet module.setAntennaEnvironmentTemperature(T_Ambient) else: if env_type == 0: # space environment T_Ambient = 150 # K elif env_type == 1: # ground station environment T_ground = 288.15 # [K] T_Ambient = T_ground # setup output message recorder objects dataLog = module.antennaOutMsg.recorder() unitTestSim.AddModelToTask(unitTaskName, dataLog) unitTestSim.InitializeSimulation() unitTestSim.ConfigureStopTime(macros.sec2nano(1.0)) unitTestSim.ExecuteSimulation() t_sky_test = module.calculateTsky() print('t_sky_test', t_sky_test) # compute truth values dir_lin = pow(10, directivity / 10) HPBW_el = np.sqrt((np.log(2) * 16) / (dir_lin * hpbwRatio)) HPBW_az = hpbwRatio * HPBW_el n = len(dataLog.HPBW_el) T_ant = (1.0 - eta_r) * T_Ambient + eta_r * T_sky_true T_S = T_E + T_ant G_TN = directivity + 10.0 * np.log10(eta_r) - 10.0 * np.log10(T_S) P_N = K_BOLTZMANN * T_S * bandwidth P_eirp = 10 * np.log10(P_Tx) + 10 * np.log10(eta_r) + directivity # Compute expected location and attitude values if env_type == 0: r_AP_N = [0.0, 0.0, 0.0] r_BN_N = [orbitalMotion.AU*1000, 0, 0] + scPos # [m] position of spacecraft in inertial frame sigma_BN = [0,0,0] dcm_BN = rbk.MRP2C(sigma_BN) r_AB_N = dcm_BN.T @ [0, 0, 1] r_AN_N = r_BN_N + r_AB_N dcm_AB = rbk.MRP2C(sigma_AB) dcm_AN = dcm_AB @ dcm_BN sigma_AN = rbk.C2MRP(dcm_AN) elif env_type == 1: r_AN_N = [orbitalMotion.AU *1000, 0, 0] r_AP_N = [orbitalMotion.REQ_EARTH*1000, 0, 0] sigma_AN = [0, 0, 0] # check results checks = [ (dataLog.environment, env_type, "environment"), (dataLog.antennaState, antennaState, "antennaState"), (dataLog.frequency, frequency, "frequency"), (dataLog.B, bandwidth, "bandwidth"), (dataLog.HPBW_el, HPBW_el, "HPBW_el"), (dataLog.HPBW_az, HPBW_az, "HPBW_az"), (dataLog.P_Tx, P_Tx, "P_Tx"), (dataLog.P_Rx, P_Rx, "P_Rx"), (dataLog.DdB, directivity, "DdB"), (dataLog.G_TN, G_TN, "G_TN"), (dataLog.P_N, P_N, "P_N"), (dataLog.P_eirp_dB, P_eirp, "P_eirp_dB"), (dataLog.T_Ambient, T_Ambient, "T_Ambient"), (dataLog.r_AN_N, r_AN_N, "r_AN_N"), (dataLog.sigma_AN, sigma_AN, "sigma_AN"), (dataLog.r_AP_N, r_AP_N, "r_AP_N"), ] # When Haslam is enabled, sky temperature is computed internally from the map, so G_TN/P_N is not validated against a hard-coded T_sky_true. if useHaslamMap: checks = [c for c in checks if c[2] not in ("G_TN", "P_N")] for data, truth, name in checks: for i in range(n): sim_val = data[i] # If sim_val is iterable (like a vector), compare component-wise if np.ndim(truth) > 0: # vector case for k in range(len(sim_val)): if not unitTestSupport.isDoubleEqual(float(sim_val[k]), float(truth[k]), ACCURACY): testFailCount += 1 testMessages.append( f"Error: {name}[{k}] not set correctly at index {i} " f"(got {sim_val[k]}, expected {truth[k]})" ) else: # scalar case if not unitTestSupport.isDoubleEqual(float(sim_val), float(truth), ACCURACY): testFailCount += 1 testMessages.append( f"Error: {name} not set correctly at index {i} " f"(got {sim_val}, expected {truth})" ) if testFailCount == 0: print("PASSED: " + module.ModelTag) else: print(testMessages) # Infer sky temperature from the module output noise power P_N. P_N_sim = float(dataLog.P_N[-1]) T_S_sim = P_N_sim / (K_BOLTZMANN * bandwidth) T_sky_sim = (T_S_sim - T_E - (1.0 - eta_r) * T_Ambient) / eta_r return [testFailCount, "".join(testMessages), T_sky_sim]
if __name__ == "__main__": test_cases = [ (simpleAntenna.ANTENNA_OFF, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RX, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_TX, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 1, 150, False, 200, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, None, False, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 1, None, False, 200, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, True, 5.45, 1.0, [0,0,0], [1,0,0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 300.0, 0.0, rbk.C2MRP(np.array([[0, 0, 1], [0, 1, 0], [-1, 0, 0]])), [1, 0, 0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 300.0, 1.0, rbk.C2MRP(np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]])), [0, 1, 0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 5.660060787285246, 1.0, rbk.C2MRP(np.array([[0, 0, -1], [0, 1, 0], [1, 0, 0]])), [1, 0, 0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 37.82128898104384, 1.0, rbk.C2MRP(np.array([[0, 0, 1], [0, 1, 0], [-1, 0, 0]])), [-1, 0, 0]), (simpleAntenna.ANTENNA_RXTX, 0, 150, False, 147.9662263230431, 0.0, earth_horizon_sigma(), [1, 0, 0]), ] print("=" * 60) print("Running all test_simpleAntenna parametrized cases") print("=" * 60) passed = 0 failed = 0 for i, (antennaState, env_type, T_AmbientSet, useHaslamMap, T_sky_true, eclipseValue, sigma_AB, scPosUnit) in enumerate(test_cases): try: test_simpleAntenna(antennaState, env_type, T_AmbientSet, useHaslamMap, T_sky_true, eclipseValue, sigma_AB, scPosUnit, ACCURACY) print(f" Case {i+1:2d}: PASSED") passed += 1 except AssertionError as e: print(f" Case {i+1:2d}: FAILED - {e}") failed += 1 print("-" * 60) print(f"test_simpleAntenna: {passed} passed, {failed} failed") print() print("=" * 60) print("Running test_simpleAntenna_haslamMap_three_runs_and_pointing_difference") print("=" * 60) # ADD THIS: try: test_simpleAntenna_haslamMap_three_runs_and_pointing_difference() print(" PASSED") except AssertionError as e: print(f" FAILED - {e}")