From 15da81b3a293d867e79cd4ea96d294845a248d87 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 23 Nov 2025 18:02:31 -0700 Subject: [PATCH 01/15] Use Pooch to fetch data files from GitHub --- requirements.txt | 1 + src/utilities/supportDataTools/__init__.py | 0 src/utilities/supportDataTools/dataFetcher.py | 182 ++++++++++++++++++ .../supportDataTools/makeRegistry.py | 75 ++++++++ 4 files changed, 258 insertions(+) create mode 100644 src/utilities/supportDataTools/__init__.py create mode 100644 src/utilities/supportDataTools/dataFetcher.py create mode 100644 src/utilities/supportDataTools/makeRegistry.py diff --git a/requirements.txt b/requirements.txt index 114277efbb..0405f699d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ pillow>=10.4.0,<=12.0.0 requests>=2.32.3,<=2.32.5 bokeh>=3.1.1,<=3.8.1 protobuf>=5.29.4,<=6.33.1 +pooch>=1.7.0,<1.9 diff --git a/src/utilities/supportDataTools/__init__.py b/src/utilities/supportDataTools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/utilities/supportDataTools/dataFetcher.py b/src/utilities/supportDataTools/dataFetcher.py new file mode 100644 index 0000000000..2ec30d81e8 --- /dev/null +++ b/src/utilities/supportDataTools/dataFetcher.py @@ -0,0 +1,182 @@ +# +# ISC License +# +# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at 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. +# + +from enum import Enum +from pathlib import Path +import functools +import requests +import pooch + +from Basilisk.utilities.supportDataTools.registrySnippet import REGISTRY +from Basilisk import __version__ + +DATA_VERSION = f"v{__version__}" + +ALBEDO_DATA_BASE_PATH = "supportData/AlbedoData/" +ATMOSPHERE_DATA_BASE_PATH = "supportData/AtmosphereData/" +DENTON_GEO_BASE_PATH = "supportData/DentonGEO/" +EPHEMERIS_DATA_BASE_PATH = "supportData/EphemerisData/" +LOCAL_GRAV_DATA_BASE_PATH = "supportData/LocalGravData/" +MAGNETIC_FIELD_BASE_PATH = "supportData/MagneticField/" + + +@functools.lru_cache(maxsize=32) +def tag_exists(tag_url: str) -> bool: + """Return True if the given GitHub tag URL exists. + + Cached so repeated calls never trigger additional network requests. + """ + try: + r = requests.head(tag_url, timeout=1) + return r.status_code == 200 + except Exception: + return False + + +def find_local_support_data() -> Path | None: + """ + Return the path to the local ``supportData`` directory if running + from a cloned repo in editable mode, otherwise return ``None``. + + Editable installs place modules under ``dist3/Basilisk/``, while the + repo's ``supportData`` directory lives at the project root. + """ + module_path = Path(__file__).resolve() + possible = module_path.parent.parent.parent / "supportData" + return possible if possible.is_dir() else None + + +# Compute the base GitHub URL once at import time. +# With the caching on `tag_exists` this avoids repeated network calls during +# file fetches. +LOCAL_SUPPORT = find_local_support_data() +if LOCAL_SUPPORT: + # Pooch will never be used because get_path() returns local files. + BASE_URL = None +else: + BASE_URL = f"https://raw.githubusercontent.com/AVSLab/basilisk/{DATA_VERSION}/" + +POOCH = pooch.create( + path=pooch.os_cache("bsk_support_data"), + base_url=BASE_URL, + registry=REGISTRY, +) + + +def get_path(file_enum: Enum) -> Path: + """ + Return a filesystem path for the requested supportData file. + + If running from a local Basilisk repo, the file is returned directly from + ``supportData/``. Otherwise, it is fetched or retrieved from the Pooch + cache. + """ + rel = relpath(file_enum) + + if LOCAL_SUPPORT: + local = LOCAL_SUPPORT / rel + if local.exists(): + return local + else: + raise FileNotFoundError(f"Support data file not found: {local}") + + return Path(POOCH.fetch(rel)) + + +class DataFile: + class AlbedoData(Enum): + Earth_ALB_2018_CERES_All_1x1 = "Earth_ALB_2018_CERES_All_1x1.csv" + Earth_ALB_2018_CERES_All_5x5 = "Earth_ALB_2018_CERES_All_5x5.csv" + Earth_ALB_2018_CERES_All_10x10 = "Earth_ALB_2018_CERES_All_10x10.csv" + Earth_ALB_2018_CERES_Clear_1x1 = "Earth_ALB_2018_CERES_Clear_1x1.csv" + Earth_ALB_2018_CERES_Clear_5x5 = "Earth_ALB_2018_CERES_Clear_5x5.csv" + Earth_ALB_2018_CERES_Clear_10x10 = "Earth_ALB_2018_CERES_Clear_10x10.csv" + earthReflectivityMean_1p25x1 = "earthReflectivityMean_1p25x1.dat" + earthReflectivityMean_5x5 = "earthReflectivityMean_5x5.dat" + earthReflectivityMean_10x10 = "earthReflectivityMean_10x10.dat" + earthReflectivityStd_1p25x1 = "earthReflectivityStd_1p25x1.dat" + earthReflectivityStd_5x5 = "earthReflectivityStd_5x5.dat" + earthReflectivityStd_10x10 = "earthReflectivityStd_10x10.dat" + Mars_ALB_TES_1x1 = "Mars_ALB_TES_1x1.csv" + Mars_ALB_TES_5x5 = "Mars_ALB_TES_5x5.csv" + Mars_ALB_TES_10x10 = "Mars_ALB_TES_10x10.csv" + marsReflectivityMean_1p25x1 = "marsReflectivityMean_1p25x1.dat" + marsReflectivityMean_5x5 = "marsReflectivityMean_5x5.dat" + marsReflectivityMean_10x10 = "marsReflectivityMean_10x10.dat" + + class AtmosphereData(Enum): + # You’re currently missing these three in the enum but they’re in REGISTRY: + EarthGRAMNominal = "EarthGRAMNominal.txt" + MarsGRAMNominal = "MarsGRAMNominal.txt" + NRLMSISE00Nominal = "NRLMSISE00Nominal.txt" + + JupiterGRAMNominal = "JupiterGRAMNominal.csv" + NeptuneGRAMNominal = "NeptuneGRAMNominal.csv" + TitanGRAMNominal = "TitanGRAMNominal.csv" + UranusGRAMNominal = "UranusGRAMNominal.csv" + USStandardAtmosphere1976 = "USStandardAtmosphere1976.csv" + VenusGRAMNominal = "VenusGRAMNominal.csv" + + class DentonGEOData(Enum): + model_e_array_all = "model_e_array_all.txt" + model_e_array_high = "model_e_array_high.txt" + model_e_array_low = "model_e_array_low.txt" + model_e_array_mid = "model_e_array_mid.txt" + model_i_array_all = "model_i_array_all.txt" + model_i_array_high = "model_i_array_high.txt" + model_i_array_low = "model_i_array_low.txt" + model_i_array_mid = "model_i_array_mid.txt" + + class EphemerisData(Enum): + de_403_masses = "de-403-masses.tpc" + de430 = "de430.bsp" + hst_edited = "hst_edited.bsp" + MVN_SCLKSCET_00000 = "MVN_SCLKSCET.00000.tsc" + naif0011 = "naif0011.tls" + naif0012 = "naif0012.tls" + nh_pred_od077 = "nh_pred_od077.bsp" + pck00010 = "pck00010.tpc" + + class LocalGravData(Enum): + eros007790 = "eros007790.tab" + GGM2BData = "GGM2BData.txt" + GGM03S_J2_only = "GGM03S-J2-only.txt" + GGM03S = "GGM03S.txt" + VESTA20H = "VESTA20H.txt" + + class MagneticFieldData(Enum): + WMM = "WMM.COF" + + +CATEGORY_BASE_PATHS = { + "AlbedoData": ALBEDO_DATA_BASE_PATH, + "AtmosphereData": ATMOSPHERE_DATA_BASE_PATH, + "DentonGEOData": DENTON_GEO_BASE_PATH, + "EphemerisData": EPHEMERIS_DATA_BASE_PATH, + "LocalGravData": LOCAL_GRAV_DATA_BASE_PATH, + "MagneticFieldData": MAGNETIC_FIELD_BASE_PATH, +} + + +def relpath(file_enum: Enum) -> str: + category_name = type(file_enum).__name__ + try: + base = CATEGORY_BASE_PATHS[category_name] + return base + file_enum.value + except KeyError: + raise ValueError(f"Unknown supportData category: {category_name}") diff --git a/src/utilities/supportDataTools/makeRegistry.py b/src/utilities/supportDataTools/makeRegistry.py new file mode 100644 index 0000000000..dc1f6748c8 --- /dev/null +++ b/src/utilities/supportDataTools/makeRegistry.py @@ -0,0 +1,75 @@ +# +# ISC License +# +# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at 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. +# + +from pathlib import Path +import hashlib + +ROOT = Path("supportData") + +IGNORE_PATTERNS = ("__pycache__", ".pyc", "__init__.py", "*.bsp") + + +def should_include(path: Path) -> bool: + name = path.name + + for pattern in IGNORE_PATTERNS: + if path.match(pattern): + return False + + if name.startswith("."): + return False + + # Skip GRAM namelist directory + if path.is_relative_to(ROOT / "AtmosphereData" / "support"): + return False + + return True + + +def md5_hex(path: Path) -> str: + h = hashlib.md5() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + h.update(chunk) + return h.hexdigest() + + +def main(): + if not ROOT.exists(): + raise RuntimeError( + "supportData directory not found. Run this script from the project root." + ) + + files = sorted(p for p in ROOT.rglob("*") if p.is_file() and should_include(p)) + + entries = [] + for path in files: + rel = path.relative_to(ROOT.parent) + digest = md5_hex(path) + entries.append(f' "{rel.as_posix()}": "md5:{digest}",') + + print("# Auto-generated registry of support data files by makeRegistry.py") + print("# DO NOT EDIT THIS FILE BY HAND. Run makeRegistry.py to update.\n") + print("REGISTRY = {") + for e in entries: + print(e) + print("}") + + +if __name__ == "__main__": + main() From 463200c850fbc2e02345a63d6873c407d042a319 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 23 Nov 2025 18:03:28 -0700 Subject: [PATCH 02/15] Add Helper to check for stale registry --- src/utilities/supportDataTools/checkSync.py | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/utilities/supportDataTools/checkSync.py diff --git a/src/utilities/supportDataTools/checkSync.py b/src/utilities/supportDataTools/checkSync.py new file mode 100644 index 0000000000..71762fe6f3 --- /dev/null +++ b/src/utilities/supportDataTools/checkSync.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# +# ISC License +# +# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at 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. +# + +from pathlib import Path +import subprocess +import sys +import tempfile +import filecmp + + +ROOT = Path(__file__).resolve().parents[3] +make_script = ROOT.joinpath("src", "utilities", "supportData", "makeRegistry.py") +registry_path = ROOT.joinpath("src", "utilities", "supportData", "registrySnippet.py") + + +def main(): + # Regenerate registry into a temporary file + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp_path = Path(tmp.name) + + result = subprocess.run( + [sys.executable, str(make_script)], + stdout=tmp_path.open("w"), + stderr=subprocess.PIPE, + text=True, + ) + + if result.returncode != 0: + print("Error running makeRegistry.py") + print(result.stderr) + return 1 + + # Compare with committed registry + if not filecmp.cmp(tmp_path, registry_path, shallow=False): + print("❌ supportData/ changed, but registrySnippet.py is outdated.") + print( + " Run: python src/utilities/supportData/makeRegistry.py > src/utilities/supportData/registrySnippet.py" + ) + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 71b78ea2e1eea1e61f59abde390c2e6651d6038d Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 23 Nov 2025 18:04:50 -0700 Subject: [PATCH 03/15] Add stale registry check to pre-commit --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8443f3b395..75d80062f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,3 +10,11 @@ repos: - id: check-case-conflict - id: check-merge-conflict - id: debug-statements +- repo: local + hooks: + - id: check-supportdata-registry + name: Ensure supportData registrySnippet.py is updated + entry: python Basilisk/utilities/supportDataTools/checkSync.py + language: python + files: ^supportData/ + pass_filenames: false From d2f0ad1108c1c189d4d8753a4d3743c10c4b9372 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 23 Nov 2025 18:06:09 -0700 Subject: [PATCH 04/15] Add __version__ field --- src/CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c4f38f425..3cd85b99b1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -715,10 +715,20 @@ if(WIN32 AND (Python3_VERSION VERSION_GREATER 3.8)) file(WRITE "${CMAKE_BINARY_DIR}/Basilisk/__init__.py" "#init file written by the build\n" "import sys, os\n" "from Basilisk import __path__\n" "bskPath = __path__[0]\n" "os.add_dll_directory(bskPath)\n" - "from Basilisk.architecture import messaging\n") + "from Basilisk.architecture import messaging\n" + "try:\n" + " from importlib.metadata import version as _pkg_version\n" + " __version__ = _pkg_version('bsk')\n" + "except Exception:\n" + " __version__ = '0.0.0'\n") else() file(WRITE "${CMAKE_BINARY_DIR}/Basilisk/__init__.py" - "from Basilisk.architecture import messaging # ensure recorders() work without first importing messaging\n") + "from Basilisk.architecture import messaging # ensure recorders() work without first importing messaging\n" + "try:\n" + " from importlib.metadata import version as _pkg_version\n" + " __version__ = _pkg_version('bsk')\n" + "except Exception:\n" + " __version__ = '0.0.0'\n") endif() # TODO: Iterate through all dist directories and add __init__.py's where they don't exist From 023d8e6e765e962e6e4682e4d75d9ca71b1b882e Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 23 Nov 2025 18:06:36 -0700 Subject: [PATCH 05/15] Add unit tests for dataFetcher --- examples/BskSim/models/BSK_Dynamics.py | 3 +- .../modelsMultiSat/BSK_EnvironmentEarth.py | 60 +- .../modelsMultiSat/BSK_EnvironmentMercury.py | 59 +- .../modelsOpNav/BSK_OpNavDynamics.py | 264 +++--- .../CNN_ImageGen/scenario_CNNImages.py | 130 ++- .../OpNavMC/scenario_LimbAttOD.py | 194 +++-- .../OpNavMC/scenario_OpNavAttODMC.py | 101 ++- examples/scenarioAerocapture.py | 153 ++-- examples/scenarioAlbedo.py | 179 +++-- examples/scenarioBasicOrbit.py | 180 +++-- examples/scenarioBasicOrbitStream.py | 175 ++-- examples/scenarioCentralBody.py | 2 +- examples/scenarioDragRendezvous.py | 400 +++++---- examples/scenarioExtendingBoom.py | 2 +- examples/scenarioFlexiblePanel.py | 324 +++++--- examples/scenarioFormationMeanOEFeedback.py | 145 +++- examples/scenarioGroundDownlink.py | 163 ++-- examples/scenarioHaloOrbit.py | 161 ++-- examples/scenarioHohmann.py | 3 +- examples/scenarioLagrangePointOrbit.py | 155 ++-- examples/scenarioMonteCarloSpice.py | 85 +- examples/scenarioOrbitMultiBody.py | 187 +++-- examples/scenarioRoboticArm.py | 272 +++++-- examples/scenarioSmallBodyLandmarks.py | 265 +++--- examples/scenarioSmallBodyNavUKF.py | 760 +++++++++++++----- examples/scenarioSpiceSpacecraft.py | 98 ++- .../_UnitTest/test_chebyPosEphem.py | 161 ++-- .../_UnitTest/test_oeStateEphem.py | 291 ++++--- .../_UnitTest/test_gravityGradient.py | 3 +- .../test_radiation_pressure_integrated.py | 3 +- .../_UnitTest/test_gravityDynEffector.py | 545 ++++++++----- .../_UnitTest/test_gravitySpacecraft.py | 252 +++--- .../test_unitTestTabularAtmosphere.py | 85 +- .../albedo/_UnitTest/test_albedo.py | 83 +- .../eclipse/_UnitTest/test_eclipse.py | 10 +- .../_UnitTest/test_unitGroundLocation.py | 4 +- .../_UnitTest/test_unitSpiceSpacecraft.py | 163 ++-- .../NBodyGravity/_UnitTest/test_gravity.py | 68 +- src/tests/test_dataFetcher.py | 68 ++ .../_UnitTests/test_scenarioBasicOrbitMC.py | 110 ++- src/utilities/planetStates.py | 70 +- src/utilities/simIncludeGravBody.py | 158 ++-- src/utilities/supportDataTools/dataFetcher.py | 12 + .../supportDataTools/registrySnippet.py | 54 ++ src/utilities/tests/test_ck_utilities.py | 56 +- src/utilities/unitTestSupport.py | 188 +++-- 46 files changed, 4544 insertions(+), 2360 deletions(-) create mode 100644 src/tests/test_dataFetcher.py create mode 100644 src/utilities/supportDataTools/registrySnippet.py diff --git a/examples/BskSim/models/BSK_Dynamics.py b/examples/BskSim/models/BSK_Dynamics.py index 6cc48daab4..0f138ba2e8 100644 --- a/examples/BskSim/models/BSK_Dynamics.py +++ b/examples/BskSim/models/BSK_Dynamics.py @@ -142,8 +142,7 @@ def SetGravityBodies(self): self.moon = 2 self.gravFactory.addBodiesTo(self.scObject) - self.gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, + self.gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) self.epochMsg = self.gravFactory.epochMsg diff --git a/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentEarth.py b/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentEarth.py index d74a8499f6..0e64e49da1 100644 --- a/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentEarth.py +++ b/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentEarth.py @@ -21,12 +21,14 @@ from Basilisk.simulation import ephemerisConverter, groundLocation, eclipse from Basilisk.topLevelModules import pyswice from Basilisk.utilities import macros as mc, simIncludeGravBody +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] class BSKEnvironmentModel: """Defines the Earth Environment.""" + def __init__(self, SimBase, envRate): # Define empty class variables self.mu = None @@ -40,7 +42,9 @@ def __init__(self, SimBase, envRate): processTasksTimeStep = mc.sec2nano(envRate) # Create task - SimBase.envProc.addTask(SimBase.CreateNewTask(self.envTaskName, processTasksTimeStep)) + SimBase.envProc.addTask( + SimBase.CreateNewTask(self.envTaskName, processTasksTimeStep) + ) # Instantiate Env modules as objects self.gravFactory = simIncludeGravBody.gravBodyFactory() @@ -65,26 +69,28 @@ def SetGravityBodies(self): Specify what gravitational bodies to include in the simulation. """ # Create gravity bodies - gravBodies = self.gravFactory.createBodies(['sun', 'earth', 'moon']) - gravBodies['earth'].isCentralBody = True - self.mu = self.gravFactory.gravBodies['earth'].mu - self.planetRadius = self.gravFactory.gravBodies['earth'].radEquator + gravBodies = self.gravFactory.createBodies(["sun", "earth", "moon"]) + gravBodies["earth"].isCentralBody = True + self.mu = self.gravFactory.gravBodies["earth"].mu + self.planetRadius = self.gravFactory.gravBodies["earth"].radEquator self.sun = 0 self.earth = 1 self.moon = 2 # Override information with SPICE timeInitString = "2021 MAY 04 07:47:48.965 (UTC)" - self.gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, - epochInMsg=True) - self.gravFactory.spiceObject.zeroBase = 'Earth' + self.gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) + self.gravFactory.spiceObject.zeroBase = "Earth" # Add pyswice instances - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) # solar system bodies + pyswice.furnsh_c(str(naif0012_path)) # leap second file + pyswice.furnsh_c(str(de403masses_path)) # solar system masses + pyswice.furnsh_c(str(pck00010_path)) # generic Planetary Constants Kernel def SetEpochObject(self): """ @@ -92,20 +98,30 @@ def SetEpochObject(self): """ # self.epochMsg = self.gravFactory.epochMsg - self.ephemObject.ModelTag = 'EphemData' - self.ephemObject.addSpiceInputMsg(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun]) - self.ephemObject.addSpiceInputMsg(self.gravFactory.spiceObject.planetStateOutMsgs[self.earth]) - self.ephemObject.addSpiceInputMsg(self.gravFactory.spiceObject.planetStateOutMsgs[self.moon]) + self.ephemObject.ModelTag = "EphemData" + self.ephemObject.addSpiceInputMsg( + self.gravFactory.spiceObject.planetStateOutMsgs[self.sun] + ) + self.ephemObject.addSpiceInputMsg( + self.gravFactory.spiceObject.planetStateOutMsgs[self.earth] + ) + self.ephemObject.addSpiceInputMsg( + self.gravFactory.spiceObject.planetStateOutMsgs[self.moon] + ) def SetEclipseObject(self): """ Specify what celestial object is causing an eclipse message. """ self.eclipseObject.ModelTag = "eclipseObject" - self.eclipseObject.sunInMsg.subscribeTo(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun]) + self.eclipseObject.sunInMsg.subscribeTo( + self.gravFactory.spiceObject.planetStateOutMsgs[self.sun] + ) # add all celestial objects in spiceObjects except for the sun (0th object) for item in range(1, len(self.gravFactory.spiceObject.planetStateOutMsgs)): - self.eclipseObject.addPlanetToModel(self.gravFactory.spiceObject.planetStateOutMsgs[item]) + self.eclipseObject.addPlanetToModel( + self.gravFactory.spiceObject.planetStateOutMsgs[item] + ) def SetGroundLocations(self): """ @@ -113,8 +129,10 @@ def SetGroundLocations(self): """ self.groundStation.ModelTag = "BoulderGroundStation" self.groundStation.planetRadius = self.planetRadius - self.groundStation.specifyLocation(np.radians(40.009971), np.radians(-105.243895), 1624) - self.groundStation.minimumElevation = np.radians(10.) + self.groundStation.specifyLocation( + np.radians(40.009971), np.radians(-105.243895), 1624 + ) + self.groundStation.minimumElevation = np.radians(10.0) self.groundStation.maximumRange = 1e9 # Global call to initialize every module diff --git a/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentMercury.py b/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentMercury.py index f51d6cd44b..395b1b3b91 100644 --- a/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentMercury.py +++ b/examples/MultiSatBskSim/modelsMultiSat/BSK_EnvironmentMercury.py @@ -21,25 +21,29 @@ from Basilisk.simulation import ephemerisConverter, groundLocation, eclipse from Basilisk.topLevelModules import pyswice from Basilisk.utilities import macros as mc, simIncludeGravBody +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] class BSKEnvironmentModel: """Defines the Mercury Environment.""" + def __init__(self, SimBase, envRate): # Define empty class variables self.mu = None self.planetRadius = None self.sun = None - self.mercury= None + self.mercury = None # Define process name, task name and task time-step self.envTaskName = "EnvironmentTask" processTasksTimeStep = mc.sec2nano(envRate) # Create task - SimBase.envProc.addTask(SimBase.CreateNewTask(self.envTaskName, processTasksTimeStep)) + SimBase.envProc.addTask( + SimBase.CreateNewTask(self.envTaskName, processTasksTimeStep) + ) # Instantiate Env modules as objects self.gravFactory = simIncludeGravBody.gravBodyFactory() @@ -64,26 +68,27 @@ def SetGravityBodies(self): Specify what gravitational bodies to include in the simulation. """ # Create gravity bodies - gravBodies = self.gravFactory.createBodies(['sun', 'mercury']) - gravBodies['mercury'].isCentralBody = True - self.mu = self.gravFactory.gravBodies['mercury'].mu - self.planetRadius = self.gravFactory.gravBodies['mercury'].radEquator + gravBodies = self.gravFactory.createBodies(["sun", "mercury"]) + gravBodies["mercury"].isCentralBody = True + self.mu = self.gravFactory.gravBodies["mercury"].mu + self.planetRadius = self.gravFactory.gravBodies["mercury"].radEquator self.sun = 0 self.mercury = 1 # Override information with SPICE timeInitString = "2012 MAY 1 00:28:30.0" - self.gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, - epochInMsg=True - ) - self.gravFactory.spiceObject.zeroBase = 'mercury' + self.gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) + self.gravFactory.spiceObject.zeroBase = "mercury" # Add pyswice instances - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) # solar system bodies + pyswice.furnsh_c(str(naif0012_path)) # leap second file + pyswice.furnsh_c(str(de403masses_path)) # solar system masses + pyswice.furnsh_c(str(pck00010_path)) # generic Planetary Constants Kernel def SetEpochObject(self): """ @@ -91,19 +96,27 @@ def SetEpochObject(self): """ # self.epochMsg = self.gravFactory.epochMsg - self.ephemObject.ModelTag = 'EphemData' - self.ephemObject.addSpiceInputMsg(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun]) - self.ephemObject.addSpiceInputMsg(self.gravFactory.spiceObject.planetStateOutMsgs[self.mercury]) + self.ephemObject.ModelTag = "EphemData" + self.ephemObject.addSpiceInputMsg( + self.gravFactory.spiceObject.planetStateOutMsgs[self.sun] + ) + self.ephemObject.addSpiceInputMsg( + self.gravFactory.spiceObject.planetStateOutMsgs[self.mercury] + ) def SetEclipseObject(self): """ Specify what celestial object is causing an eclipse message. """ self.eclipseObject.ModelTag = "eclipseObject" - self.eclipseObject.sunInMsg.subscribeTo(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun]) + self.eclipseObject.sunInMsg.subscribeTo( + self.gravFactory.spiceObject.planetStateOutMsgs[self.sun] + ) # add all celestial objects in spiceObjects except for the sun (0th object) for item in range(1, len(self.gravFactory.spiceObject.planetStateOutMsgs)): - self.eclipseObject.addPlanetToModel(self.gravFactory.spiceObject.planetStateOutMsgs[item]) + self.eclipseObject.addPlanetToModel( + self.gravFactory.spiceObject.planetStateOutMsgs[item] + ) def SetGroundLocations(self): """ @@ -111,8 +124,10 @@ def SetGroundLocations(self): """ self.groundStation.ModelTag = "GroundStation" self.groundStation.planetRadius = self.planetRadius - self.groundStation.specifyLocation(np.radians(40.009971), np.radians(-105.243895), 1624) - self.groundStation.minimumElevation = np.radians(10.) + self.groundStation.specifyLocation( + np.radians(40.009971), np.radians(-105.243895), 1624 + ) + self.groundStation.minimumElevation = np.radians(10.0) self.groundStation.maximumRange = 1e9 # Global call to initialize every module diff --git a/examples/OpNavScenarios/modelsOpNav/BSK_OpNavDynamics.py b/examples/OpNavScenarios/modelsOpNav/BSK_OpNavDynamics.py index 3f4bbad0c2..7766f69ae0 100644 --- a/examples/OpNavScenarios/modelsOpNav/BSK_OpNavDynamics.py +++ b/examples/OpNavScenarios/modelsOpNav/BSK_OpNavDynamics.py @@ -26,32 +26,42 @@ """ - import inspect import math import os import numpy as np from Basilisk import __path__ -from Basilisk.simulation import (spacecraft, extForceTorque, simpleNav, - reactionWheelStateEffector, coarseSunSensor, eclipse, - thrusterDynamicEffector, ephemerisConverter, vizInterface, - camera) +from Basilisk.simulation import ( + spacecraft, + extForceTorque, + simpleNav, + reactionWheelStateEffector, + coarseSunSensor, + eclipse, + thrusterDynamicEffector, + ephemerisConverter, + vizInterface, + camera, +) from Basilisk.topLevelModules import pyswice from Basilisk.utilities import RigidBodyKinematics as rbk from Basilisk.utilities import macros as mc from Basilisk.utilities import simIncludeThruster, simIncludeRW, simIncludeGravBody from Basilisk.utilities import unitTestSupport from Basilisk.utilities import vizSupport +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) -class BSKDynamicModels(): + +class BSKDynamicModels: """ BSK Dynamics model for the op nav simulations """ + def __init__(self, SimBase, dynRate): # define empty class variables self.cameraRate = None @@ -69,12 +79,16 @@ def __init__(self, SimBase, dynRate): self.processTasksTimeStep = mc.sec2nano(dynRate) # Create task - SimBase.dynProc.addTask(SimBase.CreateNewTask(self.taskName, self.processTasksTimeStep), 1000) - SimBase.dynProc.addTask(SimBase.CreateNewTask(self.taskCamera, mc.sec2nano(60)), 999) + SimBase.dynProc.addTask( + SimBase.CreateNewTask(self.taskName, self.processTasksTimeStep), 1000 + ) + SimBase.dynProc.addTask( + SimBase.CreateNewTask(self.taskCamera, mc.sec2nano(60)), 999 + ) # Instantiate Dyn modules as objects self.simBasePath = bskPath - self.cameraMRP_CB =[] + self.cameraMRP_CB = [] self.cameraRez = [] self.rwFactory = simIncludeRW.rwFactory() @@ -86,7 +100,9 @@ def __init__(self, SimBase, dynRate): self.eclipseObject = eclipse.Eclipse() self.CSSConstellationObject = coarseSunSensor.CSSConstellation() self.rwStateEffector = reactionWheelStateEffector.ReactionWheelStateEffector() - self.thrustersDynamicEffector = thrusterDynamicEffector.ThrusterDynamicEffector() + self.thrustersDynamicEffector = ( + thrusterDynamicEffector.ThrusterDynamicEffector() + ) self.cameraMod = camera.Camera() self.cameraMod2 = camera.Camera() self.ephemObject = ephemerisConverter.EphemerisConverter() @@ -124,7 +140,12 @@ def SetCamera(self): if self.cameraMod.saveImages: if not os.path.exists(imgFolder): os.makedirs(imgFolder) - print("Saving camera ID:" + str(self.cameraMod.cameraID) + " Images to: " + self.cameraMod.saveDir) + print( + "Saving camera ID:" + + str(self.cameraMod.cameraID) + + " Images to: " + + self.cameraMod.saveDir + ) # Noise parameters # self.cameraMod.gaussian = 2 @@ -136,16 +157,21 @@ def SetCamera(self): # Camera config self.cameraRate = 60 self.cameraMod.renderRate = int(mc.sec2nano(self.cameraRate)) # in - self.cameraMRP_CB = [0., 0., 0.] # Arbitrary camera orientation + self.cameraMRP_CB = [0.0, 0.0, 0.0] # Arbitrary camera orientation self.cameraMod.sigma_CB = self.cameraMRP_CB - self.cameraMod.cameraPos_B = [0., 0.2, 2.2] # in meters + self.cameraMod.cameraPos_B = [0.0, 0.2, 2.2] # in meters self.cameraRez = [512, 512] # [1024,1024] # in pixels - self.cameraSize = [10.*1E-3, self.cameraRez[1]/self.cameraRez[0]*10.*1E-3] # in m + self.cameraSize = [ + 10.0 * 1e-3, + self.cameraRez[1] / self.cameraRez[0] * 10.0 * 1e-3, + ] # in m self.cameraMod.resolution = self.cameraRez self.cameraMod.fieldOfView = np.deg2rad(55) self.cameraMod.parentName = self.scObject.ModelTag - self.cameraMod.skyBox = 'black' - self.cameraFocal = self.cameraSize[1]/2./np.tan(self.cameraMod.fieldOfView/2.) # in m + self.cameraMod.skyBox = "black" + self.cameraFocal = ( + self.cameraSize[1] / 2.0 / np.tan(self.cameraMod.fieldOfView / 2.0) + ) # in m def SetCamera2(self): # this 2nd camera is setup, but not used in the FSW image processing @@ -163,25 +189,32 @@ def SetCamera2(self): if self.cameraMod2.saveImages: if not os.path.exists(imgFolder): os.makedirs(imgFolder) - print("Saving camera ID:" + str(self.cameraMod2.cameraID) + " Images to: " + self.cameraMod2.saveDir) + print( + "Saving camera ID:" + + str(self.cameraMod2.cameraID) + + " Images to: " + + self.cameraMod2.saveDir + ) self.cameraMod2.blurParam = 3 # Camera config self.cameraMod2.renderRate = int(mc.sec2nano(self.cameraRate)) # in - self.cameraMod2.sigma_CB = [0., 0.5, 0.] - self.cameraMod2.cameraPos_B = [0., 0.2, 2.2] # in meters + self.cameraMod2.sigma_CB = [0.0, 0.5, 0.0] + self.cameraMod2.cameraPos_B = [0.0, 0.2, 2.2] # in meters self.cameraMod2.resolution = self.cameraRez self.cameraMod2.fieldOfView = np.deg2rad(55) self.cameraMod2.parentName = self.scObject.ModelTag - self.cameraMod2.skyBox = 'black' + self.cameraMod2.skyBox = "black" def SetVizInterface(self, SimBase): self.vizInterface = vizSupport.enableUnityVisualization( - SimBase, self.taskName, [self.scObject] + SimBase, + self.taskName, + [self.scObject], # , saveFile=__file__ - , rwEffectorList=[self.rwStateEffector] - ) + rwEffectorList=[self.rwStateEffector], + ) # setup OpNav behavior by connecting camera module config message self.vizInterface.addCamMsgToModule(self.cameraMod.cameraConfigOutMsg) self.vizInterface.addCamMsgToModule(self.cameraMod2.cameraConfigOutMsg) @@ -192,11 +225,13 @@ def SetVizInterface(self, SimBase): def SetSpacecraftHub(self): self.scObject.ModelTag = "bskSat" # -- Crate a new variable for the sim sc inertia I_sc. Note: this is currently accessed from FSWClass - self.I_sc = [900., 0., 0., - 0., 800., 0., - 0., 0., 600.] + self.I_sc = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0] self.scObject.hub.mHub = 750.0 # kg - spacecraft mass - self.scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] # m - position vector of body-fixed point B relative to CM + self.scObject.hub.r_BcB_B = [ + [0.0], + [0.0], + [0.0], + ] # m - position vector of body-fixed point B relative to CM self.scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(self.I_sc) def SetGravityEffector(self): @@ -205,33 +240,43 @@ def SetGravityEffector(self): """ timeInitString = "2019 DECEMBER 12 18:00:00.0" - gravBodies = self.gravFactory.createBodies(['sun', 'earth', 'mars barycenter', 'jupiter barycenter']) - gravBodies['mars barycenter'].isCentralBody = True + gravBodies = self.gravFactory.createBodies( + ["sun", "earth", "mars barycenter", "jupiter barycenter"] + ) + gravBodies["mars barycenter"].isCentralBody = True self.sun = 0 self.earth = 1 self.mars = 2 self.jupiter = 3 - gravBodies['mars barycenter'].useSphericalHarmonicsGravityModel( - bskPath + '/supportData/LocalGravData/GGM2BData.txt', 2) + ggm2b_path = get_path(DataFile.LocalGravData.GGM2BData) + gravBodies["mars barycenter"].useSphericalHarmonicsGravityModel( + str(ggm2b_path), 2 + ) self.gravFactory.addBodiesTo(self.scObject) - self.gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, - epochInMsg=True) + self.gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) self.gravFactory.spiceObject.referenceBase = "J2000" - self.gravFactory.spiceObject.zeroBase = 'mars barycenter' + self.gravFactory.spiceObject.zeroBase = "mars barycenter" - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.furnsh_c(self.gravFactory.spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) # solar system bodies + pyswice.furnsh_c(str(naif0012_path)) # leap second file + pyswice.furnsh_c(str(de403masses_path)) # solar system masses + pyswice.furnsh_c(str(pck00010_path)) # generic Planetary Constants Kernel def SetEclipseObject(self): - self.eclipseObject.sunInMsg.subscribeTo(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun]) + self.eclipseObject.sunInMsg.subscribeTo( + self.gravFactory.spiceObject.planetStateOutMsgs[self.sun] + ) for c in range(1, len(self.gravFactory.spiceObject.planetStateOutMsgs)): - self.eclipseObject.addPlanetToModel(self.gravFactory.spiceObject.planetStateOutMsgs[c]) + self.eclipseObject.addPlanetToModel( + self.gravFactory.spiceObject.planetStateOutMsgs[c] + ) self.eclipseObject.addSpacecraftToModel(self.scObject.scStateOutMsg) def SetExternalForceTorqueObject(self): @@ -247,14 +292,47 @@ def SetSimpleNavObject(self): sunSig = 0.1 * math.pi / 180.0 DVsig = 0.005 PMatrix = np.diag( - [posSigma, posSigma, posSigma, velSigma, velSigma, velSigma, attSigma, attSigma, attSigma, attRateSig, - attRateSig, attRateSig, sunSig, sunSig, sunSig, DVsig, DVsig, DVsig]) - errorBounds = [100000.0, 100000.0, 100000.0, # Position - 0.1, 0.1, 0.1, # Velocity - 1E-18 * math.pi / 180.0, 1E-18 * math.pi / 180.0, 1E-18 * math.pi / 180.0, # Attitude - 1E-18 * math.pi / 180.0, 1E-18 * math.pi / 180.0, 1E-18 * math.pi / 180.0, # Attitude Rate - 5.0 * math.pi / 180.0, 5.0 * math.pi / 180.0, 5.0 * math.pi / 180.0, # Sun vector - 0.5, 0.5, 0.5] # Accumulated DV + [ + posSigma, + posSigma, + posSigma, + velSigma, + velSigma, + velSigma, + attSigma, + attSigma, + attSigma, + attRateSig, + attRateSig, + attRateSig, + sunSig, + sunSig, + sunSig, + DVsig, + DVsig, + DVsig, + ] + ) + errorBounds = [ + 100000.0, + 100000.0, + 100000.0, # Position + 0.1, + 0.1, + 0.1, # Velocity + 1e-18 * math.pi / 180.0, + 1e-18 * math.pi / 180.0, + 1e-18 * math.pi / 180.0, # Attitude + 1e-18 * math.pi / 180.0, + 1e-18 * math.pi / 180.0, + 1e-18 * math.pi / 180.0, # Attitude Rate + 5.0 * math.pi / 180.0, + 5.0 * math.pi / 180.0, + 5.0 * math.pi / 180.0, # Sun vector + 0.5, + 0.5, + 0.5, + ] # Accumulated DV # PMatrix = np.zeros_like(np.eye(18)) # errorBounds = [0.0] * 18 # Accumulated DV self.SimpleNavObject.walkBounds = np.array(errorBounds) @@ -267,26 +345,30 @@ def SetSimpleNavObject(self): def SetReactionWheelDynEffector(self): """Set the 4 reaction wheel devices.""" # specify RW momentum capacity - maxRWMomentum = 50. # Nms + maxRWMomentum = 50.0 # Nms # Define orthogonal RW pyramid # -- Pointing directions - rwElAngle = np.array([40.0, 40.0, 40.0, 40.0])*mc.D2R - rwAzimuthAngle = np.array([45.0, 135.0, 225.0, 315.0])*mc.D2R - rwPosVector = [[0.8, 0.8, 1.79070], - [0.8, -0.8, 1.79070], - [-0.8, -0.8, 1.79070], - [-0.8, 0.8, 1.79070] - ] + rwElAngle = np.array([40.0, 40.0, 40.0, 40.0]) * mc.D2R + rwAzimuthAngle = np.array([45.0, 135.0, 225.0, 315.0]) * mc.D2R + rwPosVector = [ + [0.8, 0.8, 1.79070], + [0.8, -0.8, 1.79070], + [-0.8, -0.8, 1.79070], + [-0.8, 0.8, 1.79070], + ] for elAngle, azAngle, posVector in zip(rwElAngle, rwAzimuthAngle, rwPosVector): - gsHat = (rbk.Mi(-azAngle,3).dot(rbk.Mi(elAngle,2))).dot(np.array([1,0,0])) - self.rwFactory.create('Honeywell_HR16', - gsHat, - maxMomentum=maxRWMomentum, - rWB_B=posVector) + gsHat = (rbk.Mi(-azAngle, 3).dot(rbk.Mi(elAngle, 2))).dot( + np.array([1, 0, 0]) + ) + self.rwFactory.create( + "Honeywell_HR16", gsHat, maxMomentum=maxRWMomentum, rWB_B=posVector + ) - self.rwFactory.addToSpacecraft("RWStateEffector", self.rwStateEffector, self.scObject) + self.rwFactory.addToSpacecraft( + "RWStateEffector", self.rwStateEffector, self.scObject + ) def SetACSThrusterStateEffector(self): # Make a fresh TH factory instance, this is critical to run multiple times @@ -294,15 +376,15 @@ def SetACSThrusterStateEffector(self): # 8 thrusters are modeled that act in pairs to provide the desired torque thPos = [ - [825.5/1000.0, 880.3/1000.0, 1765.3/1000.0], - [825.5/1000.0, 880.3/1000.0, 260.4/1000.0], - [880.3/1000.0, 825.5/1000.0, 1765.3/1000.0], - [880.3/1000.0, 825.5/1000.0, 260.4/1000.0], - [-825.5/1000.0, -880.3/1000.0, 1765.3/1000.0], - [-825.5/1000.0, -880.3/1000.0, 260.4/1000.0], - [-880.3/1000.0, -825.5/1000.0, 1765.3/1000.0], - [-880.3/1000.0, -825.5/1000.0, 260.4/1000.0] - ] + [825.5 / 1000.0, 880.3 / 1000.0, 1765.3 / 1000.0], + [825.5 / 1000.0, 880.3 / 1000.0, 260.4 / 1000.0], + [880.3 / 1000.0, 825.5 / 1000.0, 1765.3 / 1000.0], + [880.3 / 1000.0, 825.5 / 1000.0, 260.4 / 1000.0], + [-825.5 / 1000.0, -880.3 / 1000.0, 1765.3 / 1000.0], + [-825.5 / 1000.0, -880.3 / 1000.0, 260.4 / 1000.0], + [-880.3 / 1000.0, -825.5 / 1000.0, 1765.3 / 1000.0], + [-880.3 / 1000.0, -825.5 / 1000.0, 260.4 / 1000.0], + ] thDir = [ [0.0, -1.0, 0.0], [0.0, -1.0, 0.0], @@ -311,31 +393,29 @@ def SetACSThrusterStateEffector(self): [0.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0], - [1.0, 0.0, 0.0] + [1.0, 0.0, 0.0], ] for pos_B, dir_B in zip(thPos, thDir): - thFactory.create( - 'MOOG_Monarc_1' - , pos_B - , dir_B - ) + thFactory.create("MOOG_Monarc_1", pos_B, dir_B) # create thruster object container and tie to spacecraft object - thFactory.addToSpacecraft("Thrusters", - self.thrustersDynamicEffector, - self.scObject) + thFactory.addToSpacecraft( + "Thrusters", self.thrustersDynamicEffector, self.scObject + ) def SetCSSConstellation(self): """Set the 8 CSS sensors""" self.CSSConstellationObject.ModelTag = "cssConstellation" # Create class-level registry if it doesn't exist - if not hasattr(self, '_css_registry'): + if not hasattr(self, "_css_registry"): self._css_registry = [] def setupCSS(cssDevice): - cssDevice.fov = 80. * mc.D2R # half-angle field of view value + cssDevice.fov = 80.0 * mc.D2R # half-angle field of view value cssDevice.scaleFactor = 2.0 - cssDevice.sunInMsg.subscribeTo(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun]) + cssDevice.sunInMsg.subscribeTo( + self.gravFactory.spiceObject.planetStateOutMsgs[self.sun] + ) cssDevice.stateInMsg.subscribeTo(self.scObject.scStateOutMsg) cssDevice.sunEclipseInMsg.subscribeTo(self.eclipseObject.eclipseOutMsgs[0]) # Store CSS in class-level registry @@ -344,19 +424,19 @@ def setupCSS(cssDevice): # setup CSS sensor normal vectors in body frame components nHat_B_List = [ [0.0, 0.707107, 0.707107], - [0.707107, 0., 0.707107], + [0.707107, 0.0, 0.707107], [0.0, -0.707107, 0.707107], - [-0.707107, 0., 0.707107], + [-0.707107, 0.0, 0.707107], [0.0, -0.965926, -0.258819], [-0.707107, -0.353553, -0.612372], - [0., 0.258819, -0.965926], - [0.707107, -0.353553, -0.612372] + [0.0, 0.258819, -0.965926], + [0.707107, -0.353553, -0.612372], ] numCSS = len(nHat_B_List) # store all cssList = [] - for nHat_B, i in zip(nHat_B_List, list(range(1, numCSS+1))): + for nHat_B, i in zip(nHat_B_List, list(range(1, numCSS + 1))): CSS = coarseSunSensor.CoarseSunSensor() setupCSS(CSS) CSS.ModelTag = "CSS" + str(i) @@ -368,8 +448,10 @@ def setupCSS(cssDevice): def SetEphemConvert(self): # Initialize the ephemeris module - self.ephemObject.ModelTag = 'EphemData' - self.ephemObject.addSpiceInputMsg(self.gravFactory.spiceObject.planetStateOutMsgs[self.mars]) + self.ephemObject.ModelTag = "EphemData" + self.ephemObject.addSpiceInputMsg( + self.gravFactory.spiceObject.planetStateOutMsgs[self.mars] + ) def SetSimpleGrav(self): planet = self.gravFactory.createMarsBarycenter() diff --git a/examples/OpNavScenarios/scenariosOpNav/CNN_ImageGen/scenario_CNNImages.py b/examples/OpNavScenarios/scenariosOpNav/CNN_ImageGen/scenario_CNNImages.py index f78eea8834..e3bc3786ad 100644 --- a/examples/OpNavScenarios/scenariosOpNav/CNN_ImageGen/scenario_CNNImages.py +++ b/examples/OpNavScenarios/scenariosOpNav/CNN_ImageGen/scenario_CNNImages.py @@ -22,6 +22,7 @@ This script is called by OpNavScenarios/CNN_ImageGen/OpNavMonteCarlo.py in order to generate images. """ + # Get current file path import inspect import os @@ -29,33 +30,37 @@ import sys from Basilisk.utilities import RigidBodyKinematics as rbk + # Import utilities from Basilisk.utilities import orbitalMotion, macros, unitTestSupport +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) # Import master classes: simulation base class and scenario base class -sys.path.append(path + '/../..') +sys.path.append(path + "/../..") from BSK_OpNav import BSKSim, BSKScenario import BSK_OpNavDynamics, BSK_OpNavFsw import numpy as np # Import plotting file for your scenario -sys.path.append(path + '/../../plottingOpNav') +sys.path.append(path + "/../../plottingOpNav") import OpNav_Plotting as BSK_plt + # Create your own scenario child class class scenario_OpNav(BSKSim): """Main Simulation Class""" + def __init__(self): super(scenario_OpNav, self).__init__(BSKSim) self.fswRate = 0.5 self.dynRate = 0.5 self.set_DynModel(BSK_OpNavDynamics) self.set_FswModel(BSK_OpNavFsw) - self.name = 'scenario_opnav' - self.filterUse = "bias" #"relOD" + self.name = "scenario_opnav" + self.filterUse = "bias" # "relOD" self.configure_initial_conditions() # set recorded message information @@ -69,27 +74,37 @@ def __init__(self): def configure_initial_conditions(self): # Configure Dynamics initial conditions oe = orbitalMotion.ClassicElements() - oe.a = 18000*1E3 # meters - oe.e = 0. + oe.a = 18000 * 1e3 # meters + oe.e = 0.0 oe.i = 20 * macros.D2R - oe.Omega = 25. * macros.D2R - oe.omega = 190. * macros.D2R - oe.f = 100. * macros.D2R #90 good - mu = self.get_DynModel().gravFactory.gravBodies['mars barycenter'].mu + oe.Omega = 25.0 * macros.D2R + oe.omega = 190.0 * macros.D2R + oe.f = 100.0 * macros.D2R # 90 good + mu = self.get_DynModel().gravFactory.gravBodies["mars barycenter"].mu rN, vN = orbitalMotion.elem2rv(mu, oe) orbitalMotion.rv2elem(mu, rN, vN) bias = [0, 0, -2] - MRP= [0,0,0] - if self.filterUse =="relOD": + MRP = [0, 0, 0] + if self.filterUse == "relOD": self.get_FswModel().relativeOD.stateInit = rN.tolist() + vN.tolist() if self.filterUse == "bias": - self.get_FswModel().pixelLineFilter.stateInit = rN.tolist() + vN.tolist() + bias + self.get_FswModel().pixelLineFilter.stateInit = ( + rN.tolist() + vN.tolist() + bias + ) # self.get_DynModel().scObject.hub.r_CN_NInit = rN # m - r_CN_N # self.get_DynModel().scObject.hub.v_CN_NInit = vN # m/s - v_CN_N - self.get_DynModel().scObject.hub.sigma_BNInit = [[MRP[0]], [MRP[1]], [MRP[2]]] # sigma_BN_B - self.get_DynModel().scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B + self.get_DynModel().scObject.hub.sigma_BNInit = [ + [MRP[0]], + [MRP[1]], + [MRP[2]], + ] # sigma_BN_B + self.get_DynModel().scObject.hub.omega_BN_BInit = [ + [0.0], + [0.0], + [0.0], + ] # rad/s - omega_BN_B self.get_DynModel().cameraMod.fieldOfView = np.deg2rad(55) def log_outputs(self): @@ -99,11 +114,19 @@ def log_outputs(self): # FSW process outputs samplingTime = self.get_FswModel().processTasksTimeStep - self.msgRecList[self.retainedMessageName1] = DynModel.scObject.scStateOutMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageName1]) + self.msgRecList[self.retainedMessageName1] = ( + DynModel.scObject.scStateOutMsg.recorder(samplingTime) + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageName1] + ) - self.msgRecList[self.retainedMessageName2] = FswModel.opnavCirclesMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageName2]) + self.msgRecList[self.retainedMessageName2] = FswModel.opnavCirclesMsg.recorder( + samplingTime + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageName2] + ) return @@ -115,12 +138,14 @@ def pull_outputs(self, showPlots): ## Image processing circleStates = self.scRecmsgRecList[self.retainedMessageName2] - validCircle = unitTestSupport.addTimeColumn(circleStates.times(), circleStates.valid) + validCircle = unitTestSupport.addTimeColumn( + circleStates.times(), circleStates.valid + ) sigma_CB = self.get_DynModel().cameraMRP_CB sizeMM = self.get_DynModel().cameraSize sizeOfCam = self.get_DynModel().cameraRez - focal = self.get_DynModel().cameraFocal #in m + focal = self.get_DynModel().cameraFocal # in m pixelSize = [] pixelSize.append(sizeMM[0] / sizeOfCam[0]) @@ -130,24 +155,37 @@ def pull_outputs(self, showPlots): # Plot results BSK_plt.clear_all_plots() - trueRhat_C = np.full([len(validCircle[:,0]), 4], np.nan) - trueCircles = np.full([len(validCircle[:,0]), 4], np.nan) - trueCircles[:,0] = validCircle[:,0] - trueRhat_C[:,0] = validCircle[:,0] + trueRhat_C = np.full([len(validCircle[:, 0]), 4], np.nan) + trueCircles = np.full([len(validCircle[:, 0]), 4], np.nan) + trueCircles[:, 0] = validCircle[:, 0] + trueRhat_C[:, 0] = validCircle[:, 0] ModeIdx = 0 - Rmars = 3396.19*1E3 + Rmars = 3396.19 * 1e3 for j in range(len(position_N[:, 0])): if position_N[j, 0] in validCircle[:, 0]: ModeIdx = j break - for i in range(len(validCircle[:,0])): - if validCircle[i,1] > 1E-5: - trueRhat_C[i,1:] = np.dot(np.dot(dcm_CB, rbk.MRP2C(sigma_BN[ModeIdx+i , 1:4])) ,position_N[ModeIdx+i, 1:4])/np.linalg.norm(position_N[ModeIdx+i, 1:4]) - trueCircles[i,3] = focal*np.tan(np.arcsin(Rmars/np.linalg.norm(position_N[ModeIdx+i,1:4])))/pixelSize[0] - trueRhat_C[i,1:] *= focal/trueRhat_C[i,3] - trueCircles[i, 1] = trueRhat_C[i, 1] / pixelSize[0] + sizeOfCam[0]/2 - 0.5 - trueCircles[i, 2] = trueRhat_C[i, 2] / pixelSize[1] + sizeOfCam[1]/2 - 0.5 + for i in range(len(validCircle[:, 0])): + if validCircle[i, 1] > 1e-5: + trueRhat_C[i, 1:] = np.dot( + np.dot(dcm_CB, rbk.MRP2C(sigma_BN[ModeIdx + i, 1:4])), + position_N[ModeIdx + i, 1:4], + ) / np.linalg.norm(position_N[ModeIdx + i, 1:4]) + trueCircles[i, 3] = ( + focal + * np.tan( + np.arcsin(Rmars / np.linalg.norm(position_N[ModeIdx + i, 1:4])) + ) + / pixelSize[0] + ) + trueRhat_C[i, 1:] *= focal / trueRhat_C[i, 3] + trueCircles[i, 1] = ( + trueRhat_C[i, 1] / pixelSize[0] + sizeOfCam[0] / 2 - 0.5 + ) + trueCircles[i, 2] = ( + trueRhat_C[i, 2] / pixelSize[1] + sizeOfCam[1] / 2 - 0.5 + ) return @@ -163,32 +201,40 @@ def run(TheScenario, runLog): TheScenario.get_DynModel().cameraMod.fieldOfView = np.deg2rad(55) # in degrees TheScenario.get_DynModel().cameraMod.cameraIsOn = 1 TheScenario.get_DynModel().cameraMod.saveImages = 1 - TheScenario.get_DynModel().cameraMod.saveDir = runLog.split('/')[-2] +'/' +runLog.split('/')[-1] + '/' + TheScenario.get_DynModel().cameraMod.saveDir = ( + runLog.split("/")[-2] + "/" + runLog.split("/")[-1] + "/" + ) TheScenario.get_DynModel().vizInterface.noDisplay = True # Modes: "None", "-directComm", "-noDisplay" # The following code spawns the Vizard application from python as a function of the mode selected above, and the platform. TheScenario.vizard = subprocess.Popen( - [TheScenario.vizPath, "--args", "-noDisplay", "tcp://localhost:5556"], stdout=subprocess.DEVNULL) + [TheScenario.vizPath, "--args", "-noDisplay", "tcp://localhost:5556"], + stdout=subprocess.DEVNULL, + ) print("Vizard spawned with PID = " + str(TheScenario.vizard.pid)) # Configure FSW mode - TheScenario.modeRequest = 'imageGen' + TheScenario.modeRequest = "imageGen" # Initialize simulation TheScenario.InitializeSimulation() # Configure run time and execute simulation - simulationTime = macros.min2nano(100.) + simulationTime = macros.min2nano(100.0) TheScenario.ConfigureStopTime(simulationTime) - print('Starting Execution') + print("Starting Execution") TheScenario.ExecuteSimulation() TheScenario.vizard.kill() spice = TheScenario.get_DynModel().spiceObject - spice.unloadSpiceKernel(spice.SPICEDataPath, 'de430.bsp') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'naif0012.tls') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'de-403-masses.tpc') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'pck00010.tpc') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + spice.unloadSpiceKernel(str(de430_path)) + spice.unloadSpiceKernel(str(naif0012_path)) + spice.unloadSpiceKernel(str(de403masses_path)) + spice.unloadSpiceKernel(str(pck00010_path)) return diff --git a/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_LimbAttOD.py b/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_LimbAttOD.py index 3c1e6a9f98..769944b96b 100644 --- a/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_LimbAttOD.py +++ b/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_LimbAttOD.py @@ -22,6 +22,7 @@ This script is called by OpNavScenarios/OpNavMC/MonteCarlo.py in order to make MC data. """ + # Get current file path import inspect import os @@ -29,32 +30,36 @@ import sys from Basilisk.utilities import RigidBodyKinematics as rbk + # Import utilities from Basilisk.utilities import orbitalMotion, macros, unitTestSupport +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) # Import master classes: simulation base class and scenario base class -sys.path.append(path + '/../..') +sys.path.append(path + "/../..") from BSK_OpNav import BSKSim import BSK_OpNavDynamics, BSK_OpNavFsw import numpy as np # Import plotting file for your scenario -sys.path.append(path + '/../../plottingOpNav') +sys.path.append(path + "/../../plottingOpNav") import OpNav_Plotting as BSK_plt + # Create your own scenario child class class scenario_OpNav(BSKSim): """Main Simulation Class""" + def __init__(self): super(scenario_OpNav, self).__init__(BSKSim) self.fswRate = 0.5 self.dynRate = 0.5 self.set_DynModel(BSK_OpNavDynamics) self.set_FswModel(BSK_OpNavFsw) - self.name = 'scenario_opnav' + self.name = "scenario_opnav" self.configure_initial_conditions() self.msgRecList = {} @@ -66,30 +71,40 @@ def __init__(self): def configure_initial_conditions(self): # Configure Dynamics initial conditions oe = orbitalMotion.ClassicElements() - oe.a = 18000 * 1E3 # meters + oe.a = 18000 * 1e3 # meters oe.e = 0.6 oe.i = 10 * macros.D2R - oe.Omega = 25. * macros.D2R - oe.omega = 190. * macros.D2R - oe.f = 80. * macros.D2R # 90 good - mu = self.get_DynModel().gravFactory.gravBodies['mars barycenter'].mu + oe.Omega = 25.0 * macros.D2R + oe.omega = 190.0 * macros.D2R + oe.f = 80.0 * macros.D2R # 90 good + mu = self.get_DynModel().gravFactory.gravBodies["mars barycenter"].mu rN, vN = orbitalMotion.elem2rv(mu, oe) orbitalMotion.rv2elem(mu, rN, vN) bias = [0, 0, -2] - rError= np.array([10000.,10000., -10000]) - vError= np.array([100, -10, 10]) + rError = np.array([10000.0, 10000.0, -10000]) + vError = np.array([100, -10, 10]) - MRP= [0,-0.3,0] - self.get_FswModel().relativeOD.stateInit = (rN+rError).tolist() + (vN+vError).tolist() + MRP = [0, -0.3, 0] + self.get_FswModel().relativeOD.stateInit = (rN + rError).tolist() + ( + vN + vError + ).tolist() self.get_DynModel().scObject.hub.r_CN_NInit = rN # m - r_CN_N self.get_DynModel().scObject.hub.v_CN_NInit = vN # m/s - v_CN_N - self.get_DynModel().scObject.hub.sigma_BNInit = [[MRP[0]], [MRP[1]], [MRP[2]]] # sigma_BN_B - self.get_DynModel().scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B + self.get_DynModel().scObject.hub.sigma_BNInit = [ + [MRP[0]], + [MRP[1]], + [MRP[2]], + ] # sigma_BN_B + self.get_DynModel().scObject.hub.omega_BN_BInit = [ + [0.0], + [0.0], + [0.0], + ] # rad/s - omega_BN_B qNoiseIn = np.identity(6) - qNoiseIn[0:3, 0:3] = qNoiseIn[0:3, 0:3] * 1E-3 * 1E-3 - qNoiseIn[3:6, 3:6] = qNoiseIn[3:6, 3:6] * 1E-4 * 1E-4 + qNoiseIn[0:3, 0:3] = qNoiseIn[0:3, 0:3] * 1e-3 * 1e-3 + qNoiseIn[3:6, 3:6] = qNoiseIn[3:6, 3:6] * 1e-4 * 1e-4 self.get_FswModel().relativeOD.qNoise = qNoiseIn.reshape(36).tolist() self.get_FswModel().horizonNav.noiseSF = 20 @@ -101,17 +116,33 @@ def log_outputs(self): # FSW process outputs samplingTime = self.get_FswModel().processTasksTimeStep - self.msgRecList[self.retainedMessageNameSc] = DynModel.scObject.scStateOutMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameSc]) - - self.msgRecList[self.retainedMessageNameFilt] = FswModel.relativeOD.filtDataOutMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameFilt]) - - self.msgRecList[self.retainedMessageNameOpNav] = FswModel.opnavMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameOpNav]) - - self.msgRecList[self.retainedMessageNameLimb] = FswModel.limbFinding.opnavLimbOutMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameLimb]) + self.msgRecList[self.retainedMessageNameSc] = ( + DynModel.scObject.scStateOutMsg.recorder(samplingTime) + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameSc] + ) + + self.msgRecList[self.retainedMessageNameFilt] = ( + FswModel.relativeOD.filtDataOutMsg.recorder(samplingTime) + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameFilt] + ) + + self.msgRecList[self.retainedMessageNameOpNav] = FswModel.opnavMsg.recorder( + samplingTime + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameOpNav] + ) + + self.msgRecList[self.retainedMessageNameLimb] = ( + FswModel.limbFinding.opnavLimbOutMsg.recorder(samplingTime) + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameLimb] + ) return @@ -128,7 +159,9 @@ def pull_outputs(self, showPlots): ## Image processing limbRec = self.msgRecList[self.retainedMessageNameLimb] limb = unitTestSupport.addTimeColumn(limbRec.times(), limbRec.limbPoints) - numLimbPoints = unitTestSupport.addTimeColumn(limbRec.times(), limbRec.numLimbPoints) + numLimbPoints = unitTestSupport.addTimeColumn( + limbRec.times(), limbRec.numLimbPoints + ) validLimb = unitTestSupport.addTimeColumn(limbRec.times(), limbRec.valid) ## OpNav Out @@ -148,7 +181,7 @@ def pull_outputs(self, showPlots): sigma_CB = self.get_DynModel().cameraMRP_CB sizeMM = self.get_DynModel().cameraSize sizeOfCam = self.get_DynModel().cameraRez - focal = self.get_DynModel().cameraFocal #in m + focal = self.get_DynModel().cameraFocal # in m pixelSize = [] pixelSize.append(sizeMM[0] / sizeOfCam[0]) @@ -157,26 +190,27 @@ def pull_outputs(self, showPlots): dcm_CB = rbk.MRP2C(sigma_CB) # Plot results BSK_plt.clear_all_plots() - stateError = np.zeros([len(position_N[:,0]), NUM_STATES+1]) - navCovarLong = np.full([len(position_N[:,0]), 1+NUM_STATES*NUM_STATES], np.nan) - navCovarLong[:,0] = np.copy(position_N[:,0]) + stateError = np.zeros([len(position_N[:, 0]), NUM_STATES + 1]) + navCovarLong = np.full( + [len(position_N[:, 0]), 1 + NUM_STATES * NUM_STATES], np.nan + ) + navCovarLong[:, 0] = np.copy(position_N[:, 0]) stateError[:, 0:4] = np.copy(position_N) - stateError[:,4:7] = np.copy(velocity_N[:,1:]) - measError = np.full([len(measPos[:,0]), 4], np.nan) - measError[:,0] = measPos[:,0] - measError_C = np.full([len(measPos[:,0]), 5], np.nan) - measError_C[:,0] = measPos[:,0] - trueRhat_C = np.full([len(numLimbPoints[:,0]), 4], np.nan) - trueR_C = np.full([len(numLimbPoints[:,0]), 4], np.nan) - trueCircles = np.full([len(numLimbPoints[:,0]), 4], np.nan) - trueCircles[:,0] = numLimbPoints[:,0] - trueRhat_C[:,0] = numLimbPoints[:,0] - trueR_C[:,0] = numLimbPoints[:,0] - + stateError[:, 4:7] = np.copy(velocity_N[:, 1:]) + measError = np.full([len(measPos[:, 0]), 4], np.nan) + measError[:, 0] = measPos[:, 0] + measError_C = np.full([len(measPos[:, 0]), 5], np.nan) + measError_C[:, 0] = measPos[:, 0] + trueRhat_C = np.full([len(numLimbPoints[:, 0]), 4], np.nan) + trueR_C = np.full([len(numLimbPoints[:, 0]), 4], np.nan) + trueCircles = np.full([len(numLimbPoints[:, 0]), 4], np.nan) + trueCircles[:, 0] = numLimbPoints[:, 0] + trueRhat_C[:, 0] = numLimbPoints[:, 0] + trueR_C[:, 0] = numLimbPoints[:, 0] switchIdx = 0 - Rmars = 3396.19*1E3 + Rmars = 3396.19 * 1e3 for j in range(len(stateError[:, 0])): if stateError[j, 0] in navState[:, 0]: stateError[j, 1:4] -= navState[j - switchIdx, 1:4] @@ -184,21 +218,39 @@ def pull_outputs(self, showPlots): else: stateError[j, 1:] = np.full(NUM_STATES, np.nan) switchIdx += 1 - for i in range(len(numLimbPoints[:,0])): - if numLimbPoints[i,1] > 1E-8: - measError[i, 1:4] = position_N[i +switchIdx, 1:4] - measPos[i, 1:4] - measError_C[i, 4] = np.linalg.norm(position_N[i +switchIdx, 1:4]) - np.linalg.norm(r_C[i, 1:4]) - trueR_C[i,1:] = np.dot(np.dot(dcm_CB, rbk.MRP2C(sigma_BN[i +switchIdx, 1:4])) , position_N[i +switchIdx, 1:4]) - trueRhat_C[i,1:] = np.dot(np.dot(dcm_CB, rbk.MRP2C(sigma_BN[i +switchIdx, 1:4])) ,position_N[i +switchIdx, 1:4])/np.linalg.norm(position_N[i +switchIdx, 1:4]) - trueCircles[i,3] = focal*np.tan(np.arcsin(Rmars/np.linalg.norm(position_N[i,1:4])))/pixelSize[0] - trueRhat_C[i,1:] *= focal/trueRhat_C[i,3] - measError_C[i, 1:4] = trueRhat_C[i,1:] - r_C[i, 1:4]/np.linalg.norm(r_C[i, 1:4]) - trueCircles[i, 1] = trueRhat_C[i, 1] / pixelSize[0] + sizeOfCam[0]/2 - 0.5 - trueCircles[i, 2] = trueRhat_C[i, 2] / pixelSize[1] + sizeOfCam[1]/2 - 0.5 + for i in range(len(numLimbPoints[:, 0])): + if numLimbPoints[i, 1] > 1e-8: + measError[i, 1:4] = position_N[i + switchIdx, 1:4] - measPos[i, 1:4] + measError_C[i, 4] = np.linalg.norm( + position_N[i + switchIdx, 1:4] + ) - np.linalg.norm(r_C[i, 1:4]) + trueR_C[i, 1:] = np.dot( + np.dot(dcm_CB, rbk.MRP2C(sigma_BN[i + switchIdx, 1:4])), + position_N[i + switchIdx, 1:4], + ) + trueRhat_C[i, 1:] = np.dot( + np.dot(dcm_CB, rbk.MRP2C(sigma_BN[i + switchIdx, 1:4])), + position_N[i + switchIdx, 1:4], + ) / np.linalg.norm(position_N[i + switchIdx, 1:4]) + trueCircles[i, 3] = ( + focal + * np.tan(np.arcsin(Rmars / np.linalg.norm(position_N[i, 1:4]))) + / pixelSize[0] + ) + trueRhat_C[i, 1:] *= focal / trueRhat_C[i, 3] + measError_C[i, 1:4] = trueRhat_C[i, 1:] - r_C[i, 1:4] / np.linalg.norm( + r_C[i, 1:4] + ) + trueCircles[i, 1] = ( + trueRhat_C[i, 1] / pixelSize[0] + sizeOfCam[0] / 2 - 0.5 + ) + trueCircles[i, 2] = ( + trueRhat_C[i, 2] / pixelSize[1] + sizeOfCam[1] / 2 - 0.5 + ) else: - measCovar[i,1:] = np.full(3*3, np.nan) + measCovar[i, 1:] = np.full(3 * 3, np.nan) covar_C[i, 1:] = np.full(3 * 3, np.nan) - navCovarLong[switchIdx:,:] = np.copy(navCovar) + navCovarLong[switchIdx:, :] = np.copy(navCovar) timeData = position_N[:, 0] * macros.NANO2MIN @@ -222,7 +274,6 @@ def pull_outputs(self, showPlots): def run(TheScenario): - TheScenario.log_outputs() TheScenario.configure_initial_conditions() @@ -230,31 +281,36 @@ def run(TheScenario): TheScenario.get_DynModel().vizInterface.liveStream = True vizard = subprocess.Popen( - [TheScenario.vizPath, "--args", "-directComm", - "tcp://localhost:5556"], stdout=subprocess.DEVNULL) + [TheScenario.vizPath, "--args", "-directComm", "tcp://localhost:5556"], + stdout=subprocess.DEVNULL, + ) print("Vizard spawned with PID = " + str(vizard.pid)) # Configure FSW mode - TheScenario.modeRequest = 'prepOpNav' + TheScenario.modeRequest = "prepOpNav" # Initialize simulation TheScenario.InitializeSimulation() # Configure run time and execute simulation - simulationTime = macros.min2nano(3.) + simulationTime = macros.min2nano(3.0) TheScenario.ConfigureStopTime(simulationTime) TheScenario.ExecuteSimulation() - TheScenario.modeRequest = 'OpNavAttODLimb' + TheScenario.modeRequest = "OpNavAttODLimb" # TheBSKSim.get_DynModel().SetLocalConfigData(TheBSKSim, 60, True) - simulationTime = macros.min2nano(100.) + simulationTime = macros.min2nano(100.0) TheScenario.ConfigureStopTime(simulationTime) TheScenario.ExecuteSimulation() vizard.kill() spice = TheScenario.get_DynModel().gravFactory.spiceObject - spice.unloadSpiceKernel(spice.SPICEDataPath, 'de430.bsp') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'naif0012.tls') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'de-403-masses.tpc') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'pck00010.tpc') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + spice.unloadSpiceKernel(str(de430_path)) + spice.unloadSpiceKernel(str(naif0012_path)) + spice.unloadSpiceKernel(str(de403masses_path)) + spice.unloadSpiceKernel(str(pck00010_path)) return diff --git a/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_OpNavAttODMC.py b/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_OpNavAttODMC.py index 2d9ab46eaa..8db0cc7464 100644 --- a/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_OpNavAttODMC.py +++ b/examples/OpNavScenarios/scenariosOpNav/OpNavMC/scenario_OpNavAttODMC.py @@ -22,6 +22,7 @@ This script is called by OpNavScenarios/OpNavMC/MonteCarlo.py in order to make MC data. """ + # Get current file path import inspect import os @@ -30,30 +31,32 @@ # Import utilities from Basilisk.utilities import orbitalMotion, macros, unitTestSupport +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) # Import master classes: simulation base class and scenario base class -sys.path.append(path + '/../..') +sys.path.append(path + "/../..") from BSK_OpNav import BSKSim import BSK_OpNavDynamics, BSK_OpNavFsw import numpy as np # Import plotting file for your scenario -sys.path.append(path + '/../../plottingOpNav') +sys.path.append(path + "/../../plottingOpNav") # Create your own scenario child class class scenario_OpNav(BSKSim): """Main Simulation Class""" + def __init__(self): super(scenario_OpNav, self).__init__(BSKSim) self.fswRate = 0.5 self.dynRate = 0.5 self.set_DynModel(BSK_OpNavDynamics) self.set_FswModel(BSK_OpNavFsw) - self.name = 'scenario_opnav' + self.name = "scenario_opnav" self.configure_initial_conditions() self.msgRecList = {} @@ -64,33 +67,43 @@ def __init__(self): def configure_initial_conditions(self): # Configure Dynamics initial conditions oe = orbitalMotion.ClassicElements() - oe.a = 18000 * 1E3 # meters + oe.a = 18000 * 1e3 # meters oe.e = 0.6 oe.i = 10 * macros.D2R - oe.Omega = 25. * macros.D2R - oe.omega = 190. * macros.D2R - oe.f = 80. * macros.D2R # 90 good - mu = self.get_DynModel().gravFactory.gravBodies['mars barycenter'].mu + oe.Omega = 25.0 * macros.D2R + oe.omega = 190.0 * macros.D2R + oe.f = 80.0 * macros.D2R # 90 good + mu = self.get_DynModel().gravFactory.gravBodies["mars barycenter"].mu rN, vN = orbitalMotion.elem2rv(mu, oe) orbitalMotion.rv2elem(mu, rN, vN) bias = [0, 0, -2] - rError= np.array([10000.,10000., -10000]) - vError= np.array([100, -10, 10]) - MRP= [0,0,0] - self.get_FswModel().relativeOD.stateInit = (rN + rError).tolist() + (vN + vError).tolist() + rError = np.array([10000.0, 10000.0, -10000]) + vError = np.array([100, -10, 10]) + MRP = [0, 0, 0] + self.get_FswModel().relativeOD.stateInit = (rN + rError).tolist() + ( + vN + vError + ).tolist() self.get_DynModel().scObject.hub.r_CN_NInit = rN # m - r_CN_N self.get_DynModel().scObject.hub.v_CN_NInit = vN # m/s - v_CN_N - self.get_DynModel().scObject.hub.sigma_BNInit = [[MRP[0]], [MRP[1]], [MRP[2]]] # sigma_BN_B - self.get_DynModel().scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B + self.get_DynModel().scObject.hub.sigma_BNInit = [ + [MRP[0]], + [MRP[1]], + [MRP[2]], + ] # sigma_BN_B + self.get_DynModel().scObject.hub.omega_BN_BInit = [ + [0.0], + [0.0], + [0.0], + ] # rad/s - omega_BN_B qNoiseIn = np.identity(6) - qNoiseIn[0:3, 0:3] = qNoiseIn[0:3, 0:3] * 1E-3 * 1E-3 - qNoiseIn[3:6, 3:6] = qNoiseIn[3:6, 3:6] * 1E-4 * 1E-4 + qNoiseIn[0:3, 0:3] = qNoiseIn[0:3, 0:3] * 1e-3 * 1e-3 + qNoiseIn[3:6, 3:6] = qNoiseIn[3:6, 3:6] * 1e-4 * 1e-4 self.get_FswModel().relativeOD.qNoise = qNoiseIn.reshape(36).tolist() self.get_FswModel().imageProcessing.noiseSF = 1 - self.get_FswModel().relativeOD.noiseSF = 5#7.5 + self.get_FswModel().relativeOD.noiseSF = 5 # 7.5 def log_outputs(self): # Dynamics process outputs: log messages below if desired. @@ -100,20 +113,31 @@ def log_outputs(self): # FSW process outputs samplingTime = self.get_FswModel().processTasksTimeStep - self.msgRecList[self.retainedMessageNameSc] = DynModel.scObject.scStateOutMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameSc]) - - self.msgRecList[self.retainedMessageNameFilt] = FswModel.relativeOD.filtDataOutMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameFilt]) - - self.msgRecList[self.retainedMessageNameOpNav] = FswModel.opnavMsg.recorder(samplingTime) - self.AddModelToTask(DynModel.taskName, self.msgRecList[self.retainedMessageNameOpNav]) + self.msgRecList[self.retainedMessageNameSc] = ( + DynModel.scObject.scStateOutMsg.recorder(samplingTime) + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameSc] + ) + + self.msgRecList[self.retainedMessageNameFilt] = ( + FswModel.relativeOD.filtDataOutMsg.recorder(samplingTime) + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameFilt] + ) + + self.msgRecList[self.retainedMessageNameOpNav] = FswModel.opnavMsg.recorder( + samplingTime + ) + self.AddModelToTask( + DynModel.taskName, self.msgRecList[self.retainedMessageNameOpNav] + ) return def run(TheScenario): - TheScenario.log_outputs() TheScenario.configure_initial_conditions() @@ -121,33 +145,40 @@ def run(TheScenario): TheScenario.get_DynModel().vizInterface.liveStream = True vizard = subprocess.Popen( - [TheScenario.vizPath, "--args", "-directComm", "tcp://localhost:5556"], stdout=subprocess.DEVNULL) + [TheScenario.vizPath, "--args", "-directComm", "tcp://localhost:5556"], + stdout=subprocess.DEVNULL, + ) print("Vizard spawned with PID = " + str(vizard.pid)) # Configure FSW mode - TheScenario.modeRequest = 'prepOpNav' + TheScenario.modeRequest = "prepOpNav" # Initialize simulation TheScenario.InitializeSimulation() # Configure run time and execute simulation - simulationTime = macros.min2nano(3.) + simulationTime = macros.min2nano(3.0) TheScenario.ConfigureStopTime(simulationTime) TheScenario.ExecuteSimulation() - TheScenario.modeRequest = 'OpNavAttOD' + TheScenario.modeRequest = "OpNavAttOD" # TheBSKSim.get_DynModel().SetLocalConfigData(TheBSKSim, 60, True) - simulationTime = macros.min2nano(100.) + simulationTime = macros.min2nano(100.0) TheScenario.ConfigureStopTime(simulationTime) TheScenario.ExecuteSimulation() vizard.kill() spice = TheScenario.get_DynModel().spiceObject - spice.unloadSpiceKernel(spice.SPICEDataPath, 'de430.bsp') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'naif0012.tls') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'de-403-masses.tpc') - spice.unloadSpiceKernel(spice.SPICEDataPath, 'pck00010.tpc') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + spice.unloadSpiceKernel(str(de430_path)) + spice.unloadSpiceKernel(str(naif0012_path)) + spice.unloadSpiceKernel(str(de403masses_path)) + spice.unloadSpiceKernel(str(pck00010_path)) return + if __name__ == "__main__": # Instantiate base simulation diff --git a/examples/scenarioAerocapture.py b/examples/scenarioAerocapture.py index 15a4aa6849..9ed456d234 100644 --- a/examples/scenarioAerocapture.py +++ b/examples/scenarioAerocapture.py @@ -81,13 +81,16 @@ import matplotlib.pyplot as plt import numpy as np + # The path to the location of Basilisk # Used to get the location of supporting data. from Basilisk import __path__ from Basilisk.simulation import dragDynamicEffector + # import simulation related support from Basilisk.simulation import spacecraft from Basilisk.simulation import tabularAtmosphere, simpleNav + # import general simulation support files from Basilisk.utilities import SimulationBaseClass from Basilisk.utilities import macros @@ -96,6 +99,10 @@ from Basilisk.utilities import unitTestSupport from Basilisk.utilities import vizSupport from Basilisk.utilities.readAtmTable import readAtmTable +from Basilisk.utilities.supportDataTools.dataFetcher import ( + get_path, + DataFile, +) # # Basilisk Scenario Script and Integrated Test @@ -115,28 +122,36 @@ def sph2rv(xxsph): NOTE: this function assumes inertial and planet-fixed frames are aligned at this time """ - + r = xxsph[0] lon = xxsph[1] lat = xxsph[2] u = xxsph[3] gam = xxsph[4] hda = xxsph[5] - + NI = np.eye(3) - IE = np.array([[np.cos(lat) * np.cos(lon), -np.sin(lon), -np.sin(lat) * np.cos(lon)], - [np.cos(lat) * np.sin(lon), np.cos(lon), -np.sin(lat) * np.sin(lon)], - [np.sin(lat), 0, np.cos(lat)]]) - ES = np.array([[np.cos(gam), 0, np.sin(gam)], - [-np.sin(gam) * np.sin(hda), np.cos(hda), np.cos(gam) * np.sin(hda)], - [-np.sin(gam) * np.cos(hda), -np.sin(hda), np.cos(gam) * np.cos(hda)]]) - - e1_E = np.array([1,0,0]) + IE = np.array( + [ + [np.cos(lat) * np.cos(lon), -np.sin(lon), -np.sin(lat) * np.cos(lon)], + [np.cos(lat) * np.sin(lon), np.cos(lon), -np.sin(lat) * np.sin(lon)], + [np.sin(lat), 0, np.cos(lat)], + ] + ) + ES = np.array( + [ + [np.cos(gam), 0, np.sin(gam)], + [-np.sin(gam) * np.sin(hda), np.cos(hda), np.cos(gam) * np.sin(hda)], + [-np.sin(gam) * np.cos(hda), -np.sin(hda), np.cos(gam) * np.cos(hda)], + ] + ) + + e1_E = np.array([1, 0, 0]) rvec_N = (r * NI @ IE) @ e1_E - - s3_S = np.array([0,0,1]) - uvec_N = u * ( NI @ IE @ ES) @ s3_S - + + s3_S = np.array([0, 0, 1]) + uvec_N = u * (NI @ IE @ ES) @ s3_S + return rvec_N, uvec_N @@ -163,35 +178,35 @@ def run(show_plots, planetCase): dynProcess = scSim.CreateNewProcess(simProcessName) # create the dynamics task and specify the integration update time - simulationTimeStep = macros.sec2nano(10.) + simulationTimeStep = macros.sec2nano(10.0) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # Construct algorithm and associated C++ container # change module to tabAtmo - tabAtmo = tabularAtmosphere.TabularAtmosphere() # update with current values - tabAtmo.ModelTag = "tabularAtmosphere" # update python name of test module + tabAtmo = tabularAtmosphere.TabularAtmosphere() # update with current values + tabAtmo.ModelTag = "tabularAtmosphere" # update python name of test module atmoTaskName = "atmosphere" - + # define constants & load data - if planetCase == 'Earth': + if planetCase == "Earth": r_eq = 6378136.6 - dataFileName = bskPath + '/supportData/AtmosphereData/EarthGRAMNominal.txt' - altList, rhoList, tempList = readAtmTable(dataFileName, 'EarthGRAM') + atm_path = get_path(DataFile.AtmosphereData.EarthGRAMNominal) + altList, rhoList, tempList = readAtmTable(str(atm_path), "EarthGRAM") else: r_eq = 3397.2 * 1000 - dataFileName = bskPath + '/supportData/AtmosphereData/MarsGRAMNominal.txt' - altList, rhoList, tempList = readAtmTable(dataFileName, 'MarsGRAM') - + atm_path = get_path(DataFile.AtmosphereData.MarsGRAMNominal) + altList, rhoList, tempList = readAtmTable(str(atm_path), "MarsGRAM") + # assign constants & ref. data to module tabAtmo.planetRadius = r_eq - tabAtmo.altList = tabularAtmosphere.DoubleVector(altList) + tabAtmo.altList = tabularAtmosphere.DoubleVector(altList) tabAtmo.rhoList = tabularAtmosphere.DoubleVector(rhoList) tabAtmo.tempList = tabularAtmosphere.DoubleVector(tempList) # Drag Effector projArea = 10.0 # Set drag area in m^2 dragCoeff = 2.2 # Set drag ceofficient - m_sc = 2530.0 # kg + m_sc = 2530.0 # kg dragEffector = dragDynamicEffector.DragDynamicEffector() dragEffector.ModelTag = "DragEff" @@ -199,7 +214,7 @@ def run(show_plots, planetCase): dragEffectorTaskName = "drag" dragEffector.coreParams.projectedArea = projArea dragEffector.coreParams.dragCoeff = dragCoeff - dragEffector.coreParams.comOffset = [1., 0., 0.] + dragEffector.coreParams.comOffset = [1.0, 0.0, 0.0] dynProcess.addTask(scSim.CreateNewTask(atmoTaskName, simulationTimeStep)) dynProcess.addTask(scSim.CreateNewTask(dragEffectorTaskName, simulationTimeStep)) @@ -217,7 +232,7 @@ def run(show_plots, planetCase): scObject.ModelTag = "spacecraftBody" scObject.hub.mHub = m_sc tabAtmo.addSpacecraftToModel(scObject.scStateOutMsg) - + simpleNavObj = simpleNav.SimpleNav() scSim.AddModelToTask(simTaskName, simpleNavObj) simpleNavObj.scStateInMsg.subscribeTo(scObject.scStateOutMsg) @@ -240,25 +255,25 @@ def run(show_plots, planetCase): # attach gravity model to spacecraft gravFactory.addBodiesTo(scObject) - if planetCase == 'Earth': + if planetCase == "Earth": r = 6503 * 1000 u = 11.2 * 1000 gam = -5.15 * macros.D2R else: - r = (3397.2 + 125.) * 1000 + r = (3397.2 + 125.0) * 1000 u = 6 * 1000 gam = -10 * macros.D2R lon = 0 lat = 0 - hda = np.pi/2 - xxsph = [r,lon,lat,u,gam,hda] + hda = np.pi / 2 + xxsph = [r, lon, lat, u, gam, hda] rN, vN = sph2rv(xxsph) - + scObject.hub.r_CN_NInit = rN # m - r_CN_N scObject.hub.v_CN_NInit = vN # m - v_CN_N # set the simulation time - if planetCase == 'Earth': + if planetCase == "Earth": simulationTime = macros.sec2nano(300) else: simulationTime = macros.sec2nano(400) @@ -279,9 +294,12 @@ def run(show_plots, planetCase): scObject.hub.v_CN_NInit = vN # m - v_CN_N # if this scenario is to interface with the BSK Viz, uncomment the following line - vizSupport.enableUnityVisualization(scSim, simTaskName, scObject - # , saveFile=fileName - ) + vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + # , saveFile=fileName + ) # # initialize Simulation # @@ -308,30 +326,31 @@ def run(show_plots, planetCase): plt.figure(1) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') - for idx in range(0,3): - plt.plot(dataLog.times()*macros.NANO2MIN, posData[:, idx]/1000., - color=unitTestSupport.getLineColor(idx,3), - label='$r_{BN,'+str(idx)+'}$') - plt.legend(loc='lower right') - plt.xlabel('Time [min]') - plt.ylabel('Inertial Position [km]') + ax.ticklabel_format(useOffset=False, style="plain") + for idx in range(0, 3): + plt.plot( + dataLog.times() * macros.NANO2MIN, + posData[:, idx] / 1000.0, + color=unitTestSupport.getLineColor(idx, 3), + label="$r_{BN," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [min]") + plt.ylabel("Inertial Position [km]") plt.figure(2) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") smaData = [] engData = [] for idx in range(0, len(posData)): oeData = orbitalMotion.rv2elem(mu, posData[idx, 0:3], velData[idx, 0:3]) - smaData.append(oeData.a/1000.) - engData.append(-mu/(2*oeData.a)/1e6) # km^2/s^2 - plt.plot(dataLog.times()*macros.NANO2MIN, engData - , color='#aa0000' - ) - plt.xlabel('Time [min]') - plt.ylabel('Energy [km^2/s^2]') + smaData.append(oeData.a / 1000.0) + engData.append(-mu / (2 * oeData.a) / 1e6) # km^2/s^2 + plt.plot(dataLog.times() * macros.NANO2MIN, engData, color="#aa0000") + plt.xlabel("Time [min]") + plt.ylabel("Energy [km^2/s^2]") plt.grid() pltName = fileName + "2" + planetCase figureList[pltName] = plt.figure(2) @@ -342,19 +361,19 @@ def run(show_plots, planetCase): plt.figure(3) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='sci') - plt.plot(dataNewAtmoLog.times()*macros.NANO2MIN, densData) - plt.xlabel('Time [min]') - plt.ylabel('Density in kg/m^3') + ax.ticklabel_format(useOffset=False, style="sci") + plt.plot(dataNewAtmoLog.times() * macros.NANO2MIN, densData) + plt.xlabel("Time [min]") + plt.ylabel("Density in kg/m^3") pltName = fileName + "3" + planetCase figureList[pltName] = plt.figure(3) plt.figure(4) fig = plt.gcf() ax = fig.gca() - plt.plot(v/1e3, (r-r_eq)/1e3) - plt.xlabel('velocity [km/s]') - plt.ylabel('altitude [km]') + plt.plot(v / 1e3, (r - r_eq) / 1e3) + plt.xlabel("velocity [km/s]") + plt.ylabel("altitude [km]") plt.grid() pltName = fileName + "4" + planetCase figureList[pltName] = plt.figure(4) @@ -362,9 +381,9 @@ def run(show_plots, planetCase): plt.figure(5) fig = plt.gcf() ax = fig.gca() - plt.plot(dataLog.times()*macros.NANO2MIN, (r-r_eq)/1e3) - plt.xlabel('time [min]') - plt.ylabel('altitude [km]') + plt.plot(dataLog.times() * macros.NANO2MIN, (r - r_eq) / 1e3) + plt.xlabel("time [min]") + plt.ylabel("altitude [km]") plt.grid() pltName = fileName + "5" + planetCase figureList[pltName] = plt.figure(5) @@ -376,7 +395,7 @@ def run(show_plots, planetCase): return figureList # close the plots being saved off to avoid over-writing old and new figures -if __name__ == '__main__': - run(True, 'Mars') # planet arrival case, can be Earth or Mars - - + + +if __name__ == "__main__": + run(True, "Mars") # planet arrival case, can be Earth or Mars diff --git a/examples/scenarioAlbedo.py b/examples/scenarioAlbedo.py index 45c5df3a3e..ec4e6bc3c8 100644 --- a/examples/scenarioAlbedo.py +++ b/examples/scenarioAlbedo.py @@ -137,27 +137,36 @@ import matplotlib.pyplot as plt import numpy as np + # The path to the location of Basilisk # Used to get the location of supporting data. from Basilisk import __path__ + # import message declarations from Basilisk.architecture import messaging from Basilisk.simulation import albedo from Basilisk.simulation import coarseSunSensor from Basilisk.simulation import eclipse + # import simulation related support from Basilisk.simulation import spacecraft + # import general simulation support files from Basilisk.utilities import SimulationBaseClass from Basilisk.utilities import macros, simIncludeGravBody from Basilisk.utilities import orbitalMotion as om -from Basilisk.utilities import unitTestSupport # general support file with common unit test functions +from Basilisk.utilities import ( + unitTestSupport, +) # general support file with common unit test functions +from Basilisk.utilities.supportDataTools.dataFetcher import DataFile, get_path bskPath = __path__[0] fileNameString = os.path.basename(os.path.splitext(__file__)[0]) -def run(show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, simTimeStep): +def run( + show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, simTimeStep +): """ At the end of the python script you can specify the following example parameters. @@ -180,25 +189,27 @@ def run(show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, dynProcess = scSim.CreateNewProcess(simProcessName) # Create the dynamics task if simTimeStep is None: - simulationTimeStep = macros.sec2nano(10.) + simulationTimeStep = macros.sec2nano(10.0) else: simulationTimeStep = macros.sec2nano(simTimeStep) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # Create sun message - sunPositionMsg = messaging.SpicePlanetStateMsgPayload(PositionVector=[-om.AU * 1000., 0.0, 0.0]) + sunPositionMsg = messaging.SpicePlanetStateMsgPayload( + PositionVector=[-om.AU * 1000.0, 0.0, 0.0] + ) sunMsg = messaging.SpicePlanetStateMsg().write(sunPositionMsg) # Create planet message (earth) gravFactory = simIncludeGravBody.gravBodyFactory() # Create planet message (earth) - planetCase1 = 'earth' + planetCase1 = "earth" planet1 = gravFactory.createEarth() planet1.isCentralBody = True # ensure this is the central gravitational body req1 = planet1.radEquator planetPositionMsg1 = messaging.SpicePlanetStateMsgPayload( - PositionVector=[0., 0., 0.], + PositionVector=[0.0, 0.0, 0.0], PlanetName=planetCase1, J20002Pfix=np.identity(3), ) @@ -206,9 +217,9 @@ def run(show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, pl1Msg = messaging.SpicePlanetStateMsg().write(planetPositionMsg1) if multiplePlanet: # Create planet message (moon) - planetCase2 = 'moon' + planetCase2 = "moon" planetPositionMsg2 = messaging.SpicePlanetStateMsgPayload( - PositionVector=[0., 384400. * 1000, 0.], + PositionVector=[0.0, 384400.0 * 1000, 0.0], PlanetName=planetCase2, J20002Pfix=np.identity(3), ) @@ -223,18 +234,24 @@ def run(show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, scObject.ModelTag = "bsk-Sat" rLEO = req1 + 500 * 1000 # m # Define the simulation inertia - I = [900., 0., 0., - 0., 800., 0., - 0., 0., 600.] + I = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0] scObject.hub.mHub = 750.0 # kg - spacecraft mass - scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] # m - position vector of body-fixed point B relative to CM + scObject.hub.r_BcB_B = [ + [0.0], + [0.0], + [0.0], + ] # m - position vector of body-fixed point B relative to CM scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) if multiplePlanet: # Set initial spacecraft states scObject.hub.r_CN_NInit = [[0.0], [rLEO], [0.0]] # m - r_CN_N scObject.hub.v_CN_NInit = [[0.0], [0.0], [0.0]] # m - v_CN_N scObject.hub.sigma_BNInit = [[0.0], [0.0], [0.0]] # sigma_BN_B - scObject.hub.omega_BN_BInit = [[0.0], [0.0], [1. * macros.D2R]] # rad/s - omega_BN_B + scObject.hub.omega_BN_BInit = [ + [0.0], + [0.0], + [1.0 * macros.D2R], + ] # rad/s - omega_BN_B else: # Single planet case (earth) @@ -247,13 +264,17 @@ def run(show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, rN, vN = om.elem2rv(planet1.mu, oe) # set the simulation time n = np.sqrt(planet1.mu / oe.a / oe.a / oe.a) - P = 2. * np.pi / n + P = 2.0 * np.pi / n simulationTime = macros.sec2nano(0.5 * P) # Set initial spacecraft states scObject.hub.r_CN_NInit = rN # m - r_CN_N scObject.hub.v_CN_NInit = vN # m - v_CN_N scObject.hub.sigma_BNInit = [[0.0], [0.0], [0.0]] # sigma_BN_B - scObject.hub.omega_BN_BInit = [[0.0], [0.0], [.5 * macros.D2R]] # rad/s - omega_BN_B + scObject.hub.omega_BN_BInit = [ + [0.0], + [0.0], + [0.5 * macros.D2R], + ] # rad/s - omega_BN_B gravFactory.addBodiesTo(scObject) # Add spacecraft object to the simulation process @@ -278,9 +299,9 @@ def run(show_plots, albedoData, multipleInstrument, multiplePlanet, useEclipse, def setupCSS(CSS): CSS.stateInMsg.subscribeTo(scObject.scStateOutMsg) CSS.sunInMsg.subscribeTo(sunMsg) - CSS.fov = 80. * macros.D2R + CSS.fov = 80.0 * macros.D2R CSS.maxOutput = 1.0 - CSS.nHat_B = np.array([1., 0., 0.]) + CSS.nHat_B = np.array([1.0, 0.0, 0.0]) if useEclipse: CSS.sunEclipseInMsg.subscribeTo(eclipseObject.eclipseOutMsgs[0]) @@ -292,8 +313,9 @@ def setupCSS(CSS): setupCSS(CSS1) if albedoData: - dataPath = os.path.abspath(bskPath + "/supportData/AlbedoData/") - fileName = "Earth_ALB_2018_CERES_All_5x5.csv" + albedo_path = get_path(DataFile.AlbedoData.Earth_ALB_2018_CERES_All_5x5) + dataPath = str(albedo_path.parent) + fileName = albedo_path.name albModule.addPlanetandAlbedoDataModel(pl1Msg, dataPath, fileName) else: ALB_avg = 0.5 @@ -318,14 +340,14 @@ def setupCSS(CSS): CSS2 = coarseSunSensor.CoarseSunSensor() CSS2.ModelTag = "CSS2" setupCSS(CSS2) - CSS2.nHat_B = np.array([-1., 0., 0.]) + CSS2.nHat_B = np.array([-1.0, 0.0, 0.0]) albModule.addInstrumentConfig(CSS2.fov, CSS2.nHat_B, CSS2.r_PB_B) CSS2.albedoInMsg.subscribeTo(albModule.albOutMsgs[1]) # CSS-3 CSS3 = coarseSunSensor.CoarseSunSensor() CSS3.ModelTag = "CSS3" setupCSS(CSS3) - CSS3.nHat_B = np.array([0., -1., 0.]) + CSS3.nHat_B = np.array([0.0, -1.0, 0.0]) albModule.addInstrumentConfig(CSS3.fov, CSS3.nHat_B, CSS3.r_PB_B) CSS3.albedoInMsg.subscribeTo(albModule.albOutMsgs[2]) # @@ -363,19 +385,19 @@ def setupCSS(CSS): if multiplePlanet: velRef = scObject.dynManager.getStateObject(scObject.hub.nameOfHubVelocity) # Configure a simulation stop time and execute the simulation run - T1 = macros.sec2nano(500.) + T1 = macros.sec2nano(500.0) scSim.ConfigureStopTime(T1) scSim.ExecuteSimulation() # get the current spacecraft states vVt = unitTestSupport.EigenVector3d2np(velRef.getState()) - T2 = macros.sec2nano(1000.) + T2 = macros.sec2nano(1000.0) # Set second spacecraft states for decrease in altitude vVt = vVt + [0.0, 375300, 0.0] # m - v_CN_N velRef.setState(vVt) scSim.ConfigureStopTime(T1 + T2) scSim.ExecuteSimulation() # get the current spacecraft states - T3 = macros.sec2nano(500.) + T3 = macros.sec2nano(500.0) # Set second spacecraft states for decrease in altitude vVt = [0.0, 0.0, 0.0] # m - v_CN_N velRef.setState(vVt) @@ -414,61 +436,96 @@ def setupCSS(CSS): timeAxis = dataLog.times() if multipleInstrument: for idx in range(3): - plt.plot(timeAxis * macros.NANO2SEC, dataAlb[:, idx], - linewidth=2, alpha=0.7, color=unitTestSupport.getLineColor(idx, 3), - label='Albedo$_{' + str(idx) + '}$') + plt.plot( + timeAxis * macros.NANO2SEC, + dataAlb[:, idx], + linewidth=2, + alpha=0.7, + color=unitTestSupport.getLineColor(idx, 3), + label="Albedo$_{" + str(idx) + "}$", + ) if not multiplePlanet: - plt.plot(timeAxis * macros.NANO2SEC, dataCSS[:, idx], - '--', linewidth=1.5, color=unitTestSupport.getLineColor(idx, 3), - label='CSS$_{' + str(idx) + '}$') + plt.plot( + timeAxis * macros.NANO2SEC, + dataCSS[:, idx], + "--", + linewidth=1.5, + color=unitTestSupport.getLineColor(idx, 3), + label="CSS$_{" + str(idx) + "}$", + ) else: - plt.plot(timeAxis * macros.NANO2SEC, dataAlb, - linewidth=2, alpha=0.7, color=unitTestSupport.getLineColor(0, 2), - label='Alb$_{1}$') + plt.plot( + timeAxis * macros.NANO2SEC, + dataAlb, + linewidth=2, + alpha=0.7, + color=unitTestSupport.getLineColor(0, 2), + label="Alb$_{1}$", + ) if not multiplePlanet: - plt.plot(timeAxis * macros.NANO2SEC, dataCSS, - '--', linewidth=1.5, color=unitTestSupport.getLineColor(1, 2), - label='CSS$_{1}$') + plt.plot( + timeAxis * macros.NANO2SEC, + dataCSS, + "--", + linewidth=1.5, + color=unitTestSupport.getLineColor(1, 2), + label="CSS$_{1}$", + ) if multiplePlanet: - plt.legend(loc='upper center') + plt.legend(loc="upper center") else: - plt.legend(loc='upper right') - plt.xlabel('Time [s]') - plt.ylabel('Instrument\'s signal') + plt.legend(loc="upper right") + plt.xlabel("Time [s]") + plt.ylabel("Instrument's signal") figureList = {} - pltName = fileNameString + str(1) + str(int(albedoData)) + str(int(multipleInstrument)) + str( - int(multiplePlanet)) + str( - int(useEclipse)) + pltName = ( + fileNameString + + str(1) + + str(int(albedoData)) + + str(int(multipleInstrument)) + + str(int(multiplePlanet)) + + str(int(useEclipse)) + ) figureList[pltName] = plt.figure(1) if multiplePlanet: # Show radius of SC plt.figure(2) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') - rData = np.linalg.norm(posData, axis=1) / 1000. - plt.plot(timeAxis * macros.NANO2SEC, rData, color='#aa0000') - plt.xlabel('Time [s]') - plt.ylabel('Radius [km]') - pltName = fileNameString + str(2) + str(int(albedoData)) + str(int(multipleInstrument)) + str( - int(multiplePlanet)) + str( - int(useEclipse)) + ax.ticklabel_format(useOffset=False, style="plain") + rData = np.linalg.norm(posData, axis=1) / 1000.0 + plt.plot(timeAxis * macros.NANO2SEC, rData, color="#aa0000") + plt.xlabel("Time [s]") + plt.ylabel("Radius [km]") + pltName = ( + fileNameString + + str(2) + + str(int(albedoData)) + + str(int(multipleInstrument)) + + str(int(multiplePlanet)) + + str(int(useEclipse)) + ) figureList[pltName] = plt.figure(2) if albedoData: - filePath = os.path.abspath(dataPath + '/' + fileName) - ALB1 = np.genfromtxt(filePath, delimiter=',') + filePath = os.path.abspath(dataPath + "/" + fileName) + ALB1 = np.genfromtxt(filePath, delimiter=",") # ALB coefficient figures fig = plt.figure(2) ax = fig.add_subplot(111) - ax.set_title('Earth Albedo Coefficients (All Sky)') - ax.set(xlabel='Longitude (deg)', ylabel='Latitude (deg)') - plt.imshow(ALB1, cmap='Reds', interpolation='none', extent=[-180, 180, 90, -90]) - plt.colorbar(orientation='vertical') + ax.set_title("Earth Albedo Coefficients (All Sky)") + ax.set(xlabel="Longitude (deg)", ylabel="Latitude (deg)") + plt.imshow(ALB1, cmap="Reds", interpolation="none", extent=[-180, 180, 90, -90]) + plt.colorbar(orientation="vertical") ax.set_ylim(ax.get_ylim()[::-1]) - pltName = fileNameString + str(2) + str(int(albedoData)) + str(int(multipleInstrument)) + str( - int(multiplePlanet)) + str( - int(useEclipse)) + pltName = ( + fileNameString + + str(2) + + str(int(albedoData)) + + str(int(multipleInstrument)) + + str(int(multiplePlanet)) + + str(int(useEclipse)) + ) figureList[pltName] = plt.figure(2) if show_plots: @@ -489,5 +546,5 @@ def setupCSS(CSS): True, # multipleInstrument False, # multiplePlanet True, # useEclipse - None # simTimeStep + None, # simTimeStep ) diff --git a/examples/scenarioBasicOrbit.py b/examples/scenarioBasicOrbit.py index c90540e66a..017dcb9a80 100644 --- a/examples/scenarioBasicOrbit.py +++ b/examples/scenarioBasicOrbit.py @@ -185,7 +185,6 @@ """ - # # Basilisk Scenario Script and Integrated Test # @@ -200,6 +199,7 @@ import matplotlib.pyplot as plt import numpy as np + # To play with any scenario scripts as tutorials, you should make a copy of them into a custom folder # outside of the Basilisk directory. # @@ -217,13 +217,22 @@ # import simulation related support from Basilisk.simulation import spacecraft + # general support file with common unit test functions # import general simulation support files -from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion, - simIncludeGravBody, unitTestSupport, vizSupport) +from Basilisk.utilities import ( + SimulationBaseClass, + macros, + orbitalMotion, + simIncludeGravBody, + unitTestSupport, + vizSupport, +) +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile # always import the Basilisk messaging support + def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): """ At the end of the python script you can specify the following example parameters. @@ -260,7 +269,7 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): dynProcess = scSim.CreateNewProcess(simProcessName) # create the dynamics task and specify the integration update time - simulationTimeStep = macros.sec2nano(10.) + simulationTimeStep = macros.sec2nano(10.0) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # setup the simulation tasks/objects @@ -285,20 +294,22 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): # overridden. If multiple bodies are simulated, then their positions can be # dynamically updated. See scenarioOrbitMultiBody.py to learn how this is # done via a SPICE object. - if planetCase == 'Mars': + if planetCase == "Mars": planet = gravFactory.createMarsBarycenter() - planet.isCentralBody = True # ensure this is the central gravitational body + planet.isCentralBody = True # ensure this is the central gravitational body if useSphericalHarmonics: - planet.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM2BData.txt', 100) + ggm2b_path = get_path(DataFile.LocalGravData.GGM2BData) + planet.useSphericalHarmonicsGravityModel(str(ggm2b_path), 100) else: # Earth planet = gravFactory.createEarth() - planet.isCentralBody = True # ensure this is the central gravitational body + planet.isCentralBody = True # ensure this is the central gravitational body if useSphericalHarmonics: # If extra customization is required, see the createEarth() macro to change additional values. # For example, the spherical harmonics are turned off by default. To engage them, the following code # is used - planet.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S-J2-only.txt', 2) + ggm03s_j2_only_path = get_path(DataFile.LocalGravData.GGM03S_J2_only) + planet.useSphericalHarmonicsGravityModel(str(ggm03s_j2_only_path), 2) # The value 2 indicates that the first two harmonics, excluding the 0th order harmonic, # are included. This harmonics data file only includes a zeroth order and J2 term. @@ -320,17 +331,17 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): # # setup the orbit using classical orbit elements oe = orbitalMotion.ClassicElements() - rLEO = 7000. * 1000 # meters - rGEO = 42000. * 1000 # meters - if orbitCase == 'GEO': + rLEO = 7000.0 * 1000 # meters + rGEO = 42000.0 * 1000 # meters + if orbitCase == "GEO": oe.a = rGEO oe.e = 0.00001 oe.i = 0.0 * macros.D2R - elif orbitCase == 'GTO': + elif orbitCase == "GTO": oe.a = (rLEO + rGEO) / 2.0 oe.e = 1.0 - rLEO / oe.a oe.i = 0.0 * macros.D2R - else: # LEO case, default case 0 + else: # LEO case, default case 0 oe.a = rLEO oe.e = 0.0001 oe.i = 33.3 * macros.D2R @@ -338,10 +349,11 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): oe.omega = 347.8 * macros.D2R oe.f = 85.3 * macros.D2R rN, vN = orbitalMotion.elem2rv(mu, oe) - oe = orbitalMotion.rv2elem(mu, rN, vN) # this stores consistent initial orbit elements + oe = orbitalMotion.rv2elem( + mu, rN, vN + ) # this stores consistent initial orbit elements # with circular or equatorial orbit, some angles are arbitrary - # To set the spacecraft initial conditions, the following initial position and velocity variables are set: scObject.hub.r_CN_NInit = rN # m - r_BN_N scObject.hub.v_CN_NInit = vN # m/s - v_BN_N @@ -364,9 +376,9 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): # set the simulation time n = np.sqrt(mu / oe.a / oe.a / oe.a) - P = 2. * np.pi / n + P = 2.0 * np.pi / n if useSphericalHarmonics: - simulationTime = macros.sec2nano(3. * P) + simulationTime = macros.sec2nano(3.0 * P) else: simulationTime = macros.sec2nano(0.75 * P) @@ -381,7 +393,9 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): # The recorder can be put onto a separate task with its own update rate. However, this can be # trickier to do as the recording timing must be carefully balanced with the module msg writing # to avoid recording an older message. - samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, simulationTimeStep, numDataPoints + ) # create a logging task object of the spacecraft output message at the desired down sampling ratio dataRec = scObject.scStateOutMsg.recorder(samplingTime) scSim.AddModelToTask(simTaskName, dataRec) @@ -395,10 +409,13 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): # Vizard and played back after running the BSK simulation. # To enable this, uncomment this line: - viz = vizSupport.enableUnityVisualization(scSim, simTaskName, scObject, - # saveFile=__file__ - # liveStream=True - ) + viz = vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + # saveFile=__file__ + # liveStream=True + ) # The vizInterface module must be built into BSK. This is done if the correct CMake options are selected. # The default CMake will include this vizInterface module in the BSK build. See the BSK HTML documentation on @@ -451,8 +468,18 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): # the inertial position vector components, while the second plot either shows a planar # orbit view relative to the peri-focal frame (no spherical harmonics), or the # semi-major axis time history plot (with spherical harmonics turned on). - figureList, finalDiff = plotOrbits(dataRec.times(), posData, velData, oe, mu, P, - orbitCase, useSphericalHarmonics, planetCase, planet) + figureList, finalDiff = plotOrbits( + dataRec.times(), + posData, + velData, + oe, + mu, + P, + orbitCase, + useSphericalHarmonics, + planetCase, + planet, + ) if show_plots: plt.show() @@ -463,22 +490,36 @@ def run(show_plots, orbitCase, useSphericalHarmonics, planetCase): return finalDiff, figureList -def plotOrbits(timeAxis, posData, velData, oe, mu, P, orbitCase, useSphericalHarmonics, planetCase, planet): +def plotOrbits( + timeAxis, + posData, + velData, + oe, + mu, + P, + orbitCase, + useSphericalHarmonics, + planetCase, + planet, +): # draw the inertial position vector components plt.close("all") # clears out plots from earlier test runs plt.figure(1) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") finalDiff = 0.0 for idx in range(3): - plt.plot(timeAxis * macros.NANO2SEC / P, posData[:, idx] / 1000., - color=unitTestSupport.getLineColor(idx, 3), - label='$r_{BN,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [orbits]') - plt.ylabel('Inertial Position [km]') + plt.plot( + timeAxis * macros.NANO2SEC / P, + posData[:, idx] / 1000.0, + color=unitTestSupport.getLineColor(idx, 3), + label="$r_{BN," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [orbits]") + plt.ylabel("Inertial Position [km]") figureList = {} pltName = fileName + "1" + orbitCase + str(int(useSphericalHarmonics)) + planetCase figureList[pltName] = plt.figure(1) @@ -492,10 +533,10 @@ def plotOrbits(timeAxis, posData, velData, oe, mu, P, orbitCase, useSphericalHar # draw the planet fig = plt.gcf() ax = fig.gca() - if planetCase == 'Mars': - planetColor = '#884400' + if planetCase == "Mars": + planetColor = "#884400" else: - planetColor = '#008800' + planetColor = "#008800" planetRadius = planet.radEquator / 1000 ax.add_artist(plt.Circle((0, 0), planetRadius, color=planetColor)) # draw the actual orbit @@ -505,27 +546,35 @@ def plotOrbits(timeAxis, posData, velData, oe, mu, P, orbitCase, useSphericalHar oeData = orbitalMotion.rv2elem(mu, posData[idx], velData[idx]) rData.append(oeData.rmag) fData.append(oeData.f + oeData.omega - oe.omega) - plt.plot(rData * np.cos(fData) / 1000, rData * np.sin(fData) / 1000, color='#aa0000', linewidth=3.0 - ) + plt.plot( + rData * np.cos(fData) / 1000, + rData * np.sin(fData) / 1000, + color="#aa0000", + linewidth=3.0, + ) # draw the full osculating orbit from the initial conditions fData = np.linspace(0, 2 * np.pi, 100) rData = [] for idx in range(0, len(fData)): rData.append(p / (1 + oe.e * np.cos(fData[idx]))) - plt.plot(rData * np.cos(fData) / 1000, rData * np.sin(fData) / 1000, '--', color='#555555' - ) - plt.xlabel('$i_e$ Cord. [km]') - plt.ylabel('$i_p$ Cord. [km]') + plt.plot( + rData * np.cos(fData) / 1000, + rData * np.sin(fData) / 1000, + "--", + color="#555555", + ) + plt.xlabel("$i_e$ Cord. [km]") + plt.ylabel("$i_p$ Cord. [km]") plt.grid() plt.figure(3) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") Deltar = np.empty((0, 3)) E0 = orbitalMotion.f2E(oe.f, oe.e) M0 = orbitalMotion.E2M(E0, oe.e) - n = np.sqrt(mu/(oe.a*oe.a*oe.a)) + n = np.sqrt(mu / (oe.a * oe.a * oe.a)) oe2 = copy(oe) for idx in range(0, len(posData)): M = M0 + n * timeAxis[idx] * macros.NANO2SEC @@ -534,13 +583,18 @@ def plotOrbits(timeAxis, posData, velData, oe, mu, P, orbitCase, useSphericalHar rv, vv = orbitalMotion.elem2rv(mu, oe2) Deltar = np.append(Deltar, [posData[idx] - rv], axis=0) for idx in range(3): - plt.plot(timeAxis * macros.NANO2SEC / P, Deltar[:, idx] , - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\Delta r_{BN,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [orbits]') - plt.ylabel('Trajectory Differences [m]') - pltName = fileName + "3" + orbitCase + str(int(useSphericalHarmonics)) + planetCase + plt.plot( + timeAxis * macros.NANO2SEC / P, + Deltar[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\Delta r_{BN," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [orbits]") + plt.ylabel("Trajectory Differences [m]") + pltName = ( + fileName + "3" + orbitCase + str(int(useSphericalHarmonics)) + planetCase + ) figureList[pltName] = plt.figure(3) finalDiff = np.linalg.norm(Deltar[-1]) @@ -549,24 +603,28 @@ def plotOrbits(timeAxis, posData, velData, oe, mu, P, orbitCase, useSphericalHar plt.figure(2) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") smaData = [] for idx in range(0, len(posData)): oeData = orbitalMotion.rv2elem(mu, posData[idx], velData[idx]) - smaData.append(oeData.a / 1000.) - plt.plot(timeAxis * macros.NANO2SEC / P, smaData, color='#aa0000', - ) - plt.xlabel('Time [orbits]') - plt.ylabel('SMA [km]') + smaData.append(oeData.a / 1000.0) + plt.plot( + timeAxis * macros.NANO2SEC / P, + smaData, + color="#aa0000", + ) + plt.xlabel("Time [orbits]") + plt.ylabel("SMA [km]") pltName = fileName + "2" + orbitCase + str(int(useSphericalHarmonics)) + planetCase figureList[pltName] = plt.figure(2) return figureList, finalDiff + if __name__ == "__main__": run( - True, # show_plots - 'LEO', # orbit Case (LEO, GTO, GEO) - False, # useSphericalHarmonics - 'Earth' # planetCase (Earth, Mars) + True, # show_plots + "LEO", # orbit Case (LEO, GTO, GEO) + False, # useSphericalHarmonics + "Earth", # planetCase (Earth, Mars) ) diff --git a/examples/scenarioBasicOrbitStream.py b/examples/scenarioBasicOrbitStream.py index c27f005393..c6a533cd15 100644 --- a/examples/scenarioBasicOrbitStream.py +++ b/examples/scenarioBasicOrbitStream.py @@ -88,19 +88,38 @@ # import simulation related support from Basilisk.simulation import spacecraft + # general support file with common unit test functions # import general simulation support files -from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion, - simIncludeGravBody, unitTestSupport, vizSupport, simIncludeThruster) +from Basilisk.utilities import ( + SimulationBaseClass, + macros, + orbitalMotion, + simIncludeGravBody, + unitTestSupport, + vizSupport, + simIncludeThruster, +) from Basilisk.simulation import simSynch from Basilisk.architecture import messaging from Basilisk.simulation import thrusterDynamicEffector +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + try: from Basilisk.simulation import vizInterface except ImportError: pass -def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSphericalHarmonics, planetCase): + +def run( + show_plots, + liveStream, + broadcastStream, + timeStep, + orbitCase, + useSphericalHarmonics, + planetCase, +): """ At the end of the python script you can specify the following example parameters. @@ -147,9 +166,7 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # initialize spacecraft object and set properties scObject = spacecraft.Spacecraft() scObject.ModelTag = "bskSat" - I = [60., 0., 0., - 0., 30., 0., - 0., 0., 40.] + I = [60.0, 0.0, 0.0, 0.0, 30.0, 0.0, 0.0, 0.0, 40.0] scObject.hub.mHub = 50.0 # kg - spacecraft mass scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) @@ -158,17 +175,19 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # setup Gravity Body gravFactory = simIncludeGravBody.gravBodyFactory() - if planetCase == 'Mars': + if planetCase == "Mars": planet = gravFactory.createMarsBarycenter() - planet.isCentralBody = True # ensure this is the central gravitational body + planet.isCentralBody = True # ensure this is the central gravitational body if useSphericalHarmonics: - planet.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM2BData.txt', 100) + ggm2b_path = get_path(DataFile.LocalGravData.GGM2BData) + planet.useSphericalHarmonicsGravityModel(str(ggm2b_path), 100) else: # Earth planet = gravFactory.createEarth() - planet.isCentralBody = True # ensure this is the central gravitational body + planet.isCentralBody = True # ensure this is the central gravitational body if useSphericalHarmonics: - planet.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S-J2-only.txt', 2) + ggm03s_j2_only_path = get_path(DataFile.LocalGravData.GGM03S_J2_only) + planet.useSphericalHarmonicsGravityModel(str(ggm03s_j2_only_path), 2) mu = planet.mu # attach gravity model to spacecraft @@ -179,25 +198,27 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # # setup the orbit using classical orbit elements oe = orbitalMotion.ClassicElements() - rLEO = 7000. * 1000 # meters - rGEO = 42000. * 1000 # meters - if orbitCase == 'GEO': + rLEO = 7000.0 * 1000 # meters + rGEO = 42000.0 * 1000 # meters + if orbitCase == "GEO": oe.a = rGEO oe.e = 0.00001 oe.i = 0.0 * macros.D2R - elif orbitCase == 'GTO': + elif orbitCase == "GTO": oe.a = (rLEO + rGEO) / 2.0 oe.e = 1.0 - rLEO / oe.a oe.i = 0.0 * macros.D2R - else: # LEO case, default case 0 - oe.a = 2.5*rLEO + else: # LEO case, default case 0 + oe.a = 2.5 * rLEO oe.e = 0.10 oe.i = 33.3 * macros.D2R oe.Omega = 48.2 * macros.D2R oe.omega = 347.8 * macros.D2R oe.f = 85.3 * macros.D2R rN, vN = orbitalMotion.elem2rv(mu, oe) - oe = orbitalMotion.rv2elem(mu, rN, vN) # this stores consistent initial orbit elements + oe = orbitalMotion.rv2elem( + mu, rN, vN + ) # this stores consistent initial orbit elements # with circular or equatorial orbit, some angles are arbitrary # @@ -212,7 +233,7 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # Make a fresh thruster factory instance, this is critical to run multiple times thFactory = simIncludeThruster.thrusterFactory() - thFactory.create('MOOG_Monarc_22_6', [0, 0, 0], [0, -1.5, 0]) + thFactory.create("MOOG_Monarc_22_6", [0, 0, 0], [0, -1.5, 0]) thrModelTag = "ACSThrusterDynamics" thFactory.addToSpacecraft(thrModelTag, thrusterSet, scObject) @@ -224,9 +245,9 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # set the simulation time n = np.sqrt(mu / oe.a / oe.a / oe.a) - P = 2. * np.pi / n + P = 2.0 * np.pi / n if useSphericalHarmonics: - simulationTime = macros.sec2nano(3. * P) + simulationTime = macros.sec2nano(3.0 * P) else: simulationTime = macros.sec2nano(1 * P) @@ -237,7 +258,9 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric numDataPoints = 400 else: numDataPoints = 100 - samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, simulationTimeStep, numDataPoints + ) dataLog = scObject.scStateOutMsg.recorder(samplingTime) scSim.AddModelToTask(simTaskName, dataLog) @@ -253,12 +276,15 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric scSim.AddModelToTask(simTaskName, clockSync) # Configure Vizard, using liveStream and broadcastStream options - viz = vizSupport.enableUnityVisualization(scSim, simTaskName, scObject - , thrEffectorList=thrusterSet - , thrColors=vizSupport.toRGBA255("white") - , liveStream=liveStream - , broadcastStream=broadcastStream - ) + viz = vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + thrEffectorList=thrusterSet, + thrColors=vizSupport.toRGBA255("white"), + liveStream=liveStream, + broadcastStream=broadcastStream, + ) # Set key listeners viz.settings.keyboardLiveInput = "bxpqz" @@ -318,13 +344,12 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric priorKeyPressTime = dt.datetime.now() while incrementalStopTime < simulationTime: - currState = scObject.scStateOutMsg.read() alt = np.linalg.norm(currState.r_BN_N) - planet.radEquator velNorm = np.linalg.norm(currState.v_BN_N) if vizSupport.vizFound: - hudpanel.displayString = f"HUD\nAltitude: {alt/1000:.2f} km\nInertial Velocity: {velNorm/1000:.2f} km/s" + hudpanel.displayString = f"HUD\nAltitude: {alt / 1000:.2f} km\nInertial Velocity: {velNorm / 1000:.2f} km/s" viz.vizEventDialogs.append(hudpanel) # Here, I only want to run a single BSK timestep before checking for user responses. @@ -348,20 +373,22 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # Parse keyboard inputs, perform actions now = dt.datetime.now() - if (now - priorKeyPressTime).total_seconds() > 1.0: # check that 1s has passed since last key press - if 'b' in keyInputs: + if ( + now - priorKeyPressTime + ).total_seconds() > 1.0: # check that 1s has passed since last key press + if "b" in keyInputs: print("key - b") priorKeyPressTime = now if not continueBurn: print("burn panel") viz.vizEventDialogs.append(burnpanel) - if 'q' in keyInputs: + if "q" in keyInputs: print("key - q") # Set terminateVizard flag, send to Vizard to cleanly close Vizard and end scenario viz.liveSettings.terminateVizard = True viz.UpdateState(incrementalStopTime) exit(0) - if 'x' in keyInputs: + if "x" in keyInputs: print("key - x") priorKeyPressTime = now if continueBurn: @@ -369,11 +396,11 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric continueBurn = False thrMsgData.OnTimeRequest = [0, 0, 0] thrMsg.write(thrMsgData, incrementalStopTime) - if 'z' in keyInputs: + if "z" in keyInputs: print("key - z") priorKeyPressTime = now vizSupport.endFlag = True # End scenario - if 'p' in keyInputs: + if "p" in keyInputs: print("key - p") priorKeyPressTime = now vizSupport.pauseFlag = not vizSupport.pauseFlag # Toggle @@ -392,7 +419,7 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric print("Cancelling burn.") # Append info panel - if incrementalStopTime == 100*simulationTimeStep: + if incrementalStopTime == 100 * simulationTimeStep: viz.vizEventDialogs.append(infopanel) # Turn on thrusters @@ -416,16 +443,19 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric plt.figure(1) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") for idx in range(3): - plt.plot(dataLog.times() * macros.NANO2SEC / P, posData[:, idx] / 1000., - color=unitTestSupport.getLineColor(idx, 3), - label='$r_{BN,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [orbits]') - plt.ylabel('Inertial Position [km]') + plt.plot( + dataLog.times() * macros.NANO2SEC / P, + posData[:, idx] / 1000.0, + color=unitTestSupport.getLineColor(idx, 3), + label="$r_{BN," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [orbits]") + plt.ylabel("Inertial Position [km]") figureList = {} - pltName = fileName + "1" + orbitCase + str(int(useSphericalHarmonics))+ planetCase + pltName = fileName + "1" + orbitCase + str(int(useSphericalHarmonics)) + planetCase figureList[pltName] = plt.figure(1) if useSphericalHarmonics is False: @@ -437,10 +467,10 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # draw the planet fig = plt.gcf() ax = fig.gca() - if planetCase == 'Mars': - planetColor = '#884400' + if planetCase == "Mars": + planetColor = "#884400" else: - planetColor = '#008800' + planetColor = "#008800" planetRadius = planet.radEquator / 1000 ax.add_artist(plt.Circle((0, 0), planetRadius, color=planetColor)) # draw the actual orbit @@ -450,32 +480,43 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric oeData = orbitalMotion.rv2elem(mu, posData[idx], velData[idx]) rData.append(oeData.rmag) fData.append(oeData.f + oeData.omega - oe.omega) - plt.plot(rData * np.cos(fData) / 1000, rData * np.sin(fData) / 1000, color='#aa0000', linewidth=3.0 - ) + plt.plot( + rData * np.cos(fData) / 1000, + rData * np.sin(fData) / 1000, + color="#aa0000", + linewidth=3.0, + ) # draw the full osculating orbit from the initial conditions fData = np.linspace(0, 2 * np.pi, 100) rData = [] for idx in range(0, len(fData)): rData.append(p / (1 + oe.e * np.cos(fData[idx]))) - plt.plot(rData * np.cos(fData) / 1000, rData * np.sin(fData) / 1000, '--', color='#555555' - ) - plt.xlabel('$i_e$ Cord. [km]') - plt.ylabel('$i_p$ Cord. [km]') + plt.plot( + rData * np.cos(fData) / 1000, + rData * np.sin(fData) / 1000, + "--", + color="#555555", + ) + plt.xlabel("$i_e$ Cord. [km]") + plt.ylabel("$i_p$ Cord. [km]") plt.grid() else: plt.figure(2) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") smaData = [] for idx in range(0, len(posData)): oeData = orbitalMotion.rv2elem(mu, posData[idx], velData[idx]) - smaData.append(oeData.a / 1000.) - plt.plot(posData[:, 0] * macros.NANO2SEC / P, smaData, color='#aa0000', - ) - plt.xlabel('Time [orbits]') - plt.ylabel('SMA [km]') + smaData.append(oeData.a / 1000.0) + plt.plot( + posData[:, 0] * macros.NANO2SEC / P, + smaData, + color="#aa0000", + ) + plt.xlabel("Time [orbits]") + plt.ylabel("SMA [km]") pltName = fileName + "2" + orbitCase + str(int(useSphericalHarmonics)) + planetCase figureList[pltName] = plt.figure(2) @@ -495,11 +536,11 @@ def run(show_plots, liveStream, broadcastStream, timeStep, orbitCase, useSpheric # if __name__ == "__main__": run( - False, # show_plots - True, # liveStream - True, # broadcastStream - 1.0, # time step (s) - 'LEO', # orbit Case (LEO, GTO, GEO) - False, # useSphericalHarmonics - 'Earth' # planetCase (Earth, Mars) + False, # show_plots + True, # liveStream + True, # broadcastStream + 1.0, # time step (s) + "LEO", # orbit Case (LEO, GTO, GEO) + False, # useSphericalHarmonics + "Earth", # planetCase (Earth, Mars) ) diff --git a/examples/scenarioCentralBody.py b/examples/scenarioCentralBody.py index 23346b44c8..65420706ba 100644 --- a/examples/scenarioCentralBody.py +++ b/examples/scenarioCentralBody.py @@ -191,7 +191,7 @@ def run(show_plots, useCentral): #by default planetstates.planetPositionVelocity returns SSB central ICRS coordinates for the planet at the time # requested. also pck0010.tpc ephemeris file #look in the function for how to use other ephemeris files, reference frames, and observers - planetPosition, planetVelocity = planetStates.planetPositionVelocity('EARTH', UTCInit) + planetPosition, planetVelocity = planetStates.planetPositionVelocity('EARTH', UTCInit, gravFactory.spiceObject.SPICEDataPath) scObject.hub.r_CN_NInit = rN + array(planetPosition) scObject.hub.v_CN_NInit = vN + array(planetVelocity) # In the above call, the first input is the planet to get the states of and the second is the UTC time diff --git a/examples/scenarioDragRendezvous.py b/examples/scenarioDragRendezvous.py index 3986ba2240..694b774644 100644 --- a/examples/scenarioDragRendezvous.py +++ b/examples/scenarioDragRendezvous.py @@ -80,7 +80,12 @@ import numpy as np from Basilisk import __path__ from Basilisk.fswAlgorithms import hillStateConverter, hillToAttRef, hillPoint -from Basilisk.simulation import spacecraft, facetDragDynamicEffector, simpleNav, exponentialAtmosphere +from Basilisk.simulation import ( + spacecraft, + facetDragDynamicEffector, + simpleNav, + exponentialAtmosphere, +) from Basilisk.utilities import RigidBodyKinematics as rbk from Basilisk.utilities import SimulationBaseClass from Basilisk.utilities import macros @@ -89,41 +94,130 @@ from Basilisk.utilities import unitTestSupport from Basilisk.utilities import vizSupport from Basilisk.architecture import astroConstants +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] fileName = os.path.basename(os.path.splitext(__file__)[0]) # Declare some linearized drag HCW dynamics -drag_state_dynamics = [[0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,1.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,1.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [4.134628279603025589e-06,0.000000000000000000e+00,0.000000000000000000e+00,-7.178791202675993545e-10,2.347943292785702706e-03,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,-2.347943292785702706e-03,-1.435758240535198709e-09,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00]] -drag_ctrl_effects = [[0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,4.1681080996120926e-05], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00] - ] -drag_sens_effects = [[0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,-7.178791202675151888e-10,0.000000000000000000e+00,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,-1.435758240535030378e-09,0.000000000000000000e+00], - [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00]] -drag_R_inv = [[1e-8,0,0], - [0,1e-8,0], - [0,0,1e-8]] +drag_state_dynamics = [ + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 1.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 1.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 4.134628279603025589e-06, + 0.000000000000000000e00, + 0.000000000000000000e00, + -7.178791202675993545e-10, + 2.347943292785702706e-03, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + -2.347943292785702706e-03, + -1.435758240535198709e-09, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], +] +drag_ctrl_effects = [ + [0.000000000000000000e00, 0.000000000000000000e00, 0.000000000000000000e00], + [0.000000000000000000e00, 0.000000000000000000e00, 0.000000000000000000e00], + [0.000000000000000000e00, 0.000000000000000000e00, 0.000000000000000000e00], + [0.000000000000000000e00, 0.000000000000000000e00, 0.000000000000000000e00], + [0.000000000000000000e00, 0.000000000000000000e00, 4.1681080996120926e-05], + [0.000000000000000000e00, 0.000000000000000000e00, 0.000000000000000000e00], +] +drag_sens_effects = [ + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + -7.178791202675151888e-10, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + -1.435758240535030378e-09, + 0.000000000000000000e00, + ], + [ + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + 0.000000000000000000e00, + ], +] +drag_R_inv = [[1e-8, 0, 0], [0, 1e-8, 0], [0, 0, 1e-8]] # Static LQR gain path = os.path.dirname(os.path.abspath(__file__)) dataFileName = os.path.join(path, "dataForExamples", "static_lqr_controlGain.npz") -lqr_gain_set = np.load(dataFileName)['arr_0.npy'] +lqr_gain_set = np.load(dataFileName)["arr_0.npy"] fileName = os.path.basename(os.path.splitext(__file__)[0]) + def setup_spacecraft_plant(rN, vN, modelName): """ Convenience function to set up a spacecraft and its associated dragEffector and simpleNav module. @@ -139,30 +233,30 @@ def setup_spacecraft_plant(rN, vN, modelName): scObject.ModelTag = modelName scObject.hub.mHub = 6.0 scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] - I = [10., 0., 0., - 0., 9., 0., - 0., 0., 8.] + I = [10.0, 0.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0, 8.0] scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) scObject.hub.r_CN_NInit = rN scObject.hub.v_CN_NInit = vN scNav = simpleNav.SimpleNav() - scNav.ModelTag = modelName+'_navigator' + scNav.ModelTag = modelName + "_navigator" scNav.scStateInMsg.subscribeTo(scObject.scStateOutMsg) - dragArea = 0.3*0.2 + dragArea = 0.3 * 0.2 dragCoeff = 2.2 - normalVector = -rbk.euler3(np.radians(45.)).dot(np.array([0,-1,0])) - panelLocation = [0,0,0] + normalVector = -rbk.euler3(np.radians(45.0)).dot(np.array([0, -1, 0])) + panelLocation = [0, 0, 0] dragEffector = facetDragDynamicEffector.FacetDragDynamicEffector() - dragEffector.ModelTag = modelName+'_dragEffector' + dragEffector.ModelTag = modelName + "_dragEffector" dragEffector.addFacet(dragArea, dragCoeff, normalVector, panelLocation) scObject.addDynamicEffector(dragEffector) return scObject, dragEffector, scNav -def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', useJ2=False): +def drag_simulator( + altOffset, trueAnomOffset, densMultiplier, ctrlType="lqr", useJ2=False +): """ Basilisk simulation of a two-spacecraft rendezvous using relative-attitude driven differential drag. Includes both static gain and desensitized time-varying gain options and the option to use simulated attitude control or @@ -174,7 +268,9 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us """ startTime = time.time() - print(f"Starting process execution for altOffset = {altOffset}, trueAnomOffset={trueAnomOffset}, densMultiplier={densMultiplier} with {ctrlType} controls...") + print( + f"Starting process execution for altOffset = {altOffset}, trueAnomOffset={trueAnomOffset}, densMultiplier={densMultiplier} with {ctrlType} controls..." + ) scSim = SimulationBaseClass.SimBaseClass() @@ -195,7 +291,8 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us earth.isCentralBody = True mu = earth.mu if useJ2: - earth.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S.txt', 2) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + earth.useSphericalHarmonicsGravityModel(str(ggm03s_path), 2) # timeInitString = '2021 MAY 04 07:47:48.965 (UTC)' # spiceObject = gravFactory.createSpiceInterface(time=timeInitString) @@ -203,37 +300,37 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us # Density atmosphere = exponentialAtmosphere.ExponentialAtmosphere() - atmosphere.ModelTag = 'atmosphere' + atmosphere.ModelTag = "atmosphere" # atmosphere.planetPosInMsg.subscribeTo(spiceObject.planetStateOutMsgs[0]) - atmosphere.planetRadius = astroConstants.REQ_EARTH*1e3 + 300e3 # m + atmosphere.planetRadius = astroConstants.REQ_EARTH * 1e3 + 300e3 # m atmosphere.envMinReach = -300e3 atmosphere.envMaxReach = +300e3 - atmosphere.scaleHeight = 8.0e3 # m - atmosphere.baseDensity = 2.022E-14 * 1000 * densMultiplier # kg/m^3 + atmosphere.scaleHeight = 8.0e3 # m + atmosphere.baseDensity = 2.022e-14 * 1000 * densMultiplier # kg/m^3 ## Set up chief, deputy orbits: chief_oe = orbitalMotion.ClassicElements() - chief_oe.a = astroConstants.REQ_EARTH * 1e3 + 300e3 # meters - chief_oe.e = 0. - chief_oe.i = np.radians(45.) + chief_oe.a = astroConstants.REQ_EARTH * 1e3 + 300e3 # meters + chief_oe.e = 0.0 + chief_oe.i = np.radians(45.0) chief_oe.Omega = np.radians(20.0) - chief_oe.omega = np.radians(30.) - chief_oe.f = np.radians(20.) + chief_oe.omega = np.radians(30.0) + chief_oe.f = np.radians(20.0) chief_rN, chief_vN = orbitalMotion.elem2rv(mu, chief_oe) dep_oe = orbitalMotion.ClassicElements() dep_oe.a = orbitalMotion.REQ_EARTH * 1e3 + 300e3 + (altOffset) # meters - dep_oe.e = 0. - dep_oe.i = np.radians(45.) + dep_oe.e = 0.0 + dep_oe.i = np.radians(45.0) dep_oe.Omega = np.radians(20.0) - dep_oe.omega = np.radians(30.) - dep_oe.f = np.radians(20. - trueAnomOffset) + dep_oe.omega = np.radians(30.0) + dep_oe.f = np.radians(20.0 - trueAnomOffset) dep_rN, dep_vN = orbitalMotion.elem2rv(mu, dep_oe) # Initialize s/c dynamics, drag, navigation solutions - chiefSc, chiefDrag, chiefNav = setup_spacecraft_plant(chief_rN, chief_vN, 'wiggum') - depSc, depDrag, depNav = setup_spacecraft_plant(dep_rN,dep_vN, 'lou') + chiefSc, chiefDrag, chiefNav = setup_spacecraft_plant(chief_rN, chief_vN, "wiggum") + depSc, depDrag, depNav = setup_spacecraft_plant(dep_rN, dep_vN, "lou") # Connect s/c to environment (gravity, density) gravFactory.addBodiesTo(chiefSc) @@ -245,7 +342,7 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us chiefDrag.atmoDensInMsg.subscribeTo(atmosphere.envOutMsgs[-1]) # Add all dynamics stuff to dynamics task - scSim.AddModelToTask(dynTaskName, atmosphere,920) + scSim.AddModelToTask(dynTaskName, atmosphere, 920) # scSim.AddModelToTask(dynTaskName, ephemConverter, 921) scSim.AddModelToTask(dynTaskName, chiefDrag, 890) scSim.AddModelToTask(dynTaskName, depDrag, 891) @@ -259,16 +356,18 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us # Chief S/C # hillPoint - set such that the chief attitude follows its hill frame. chiefAttRef = hillPoint.hillPoint() - chiefAttRef.ModelTag = 'chief_att_ref' + chiefAttRef.ModelTag = "chief_att_ref" chiefAttRef.transNavInMsg.subscribeTo(chiefNav.transOutMsg) # chiefAttRefData.celBodyInMsg.subscribeTo(ephemConverter.ephemOutMsgs[-1]) # We shouldn't need this because the planet is the origin - chiefSc.attRefInMsg.subscribeTo(chiefAttRef.attRefOutMsg) # Force the chief spacecraft to follow the hill direction + chiefSc.attRefInMsg.subscribeTo( + chiefAttRef.attRefOutMsg + ) # Force the chief spacecraft to follow the hill direction depHillRef = hillPoint.hillPoint() - depHillRef.ModelTag = 'dep_hill_ref' + depHillRef.ModelTag = "dep_hill_ref" depHillRef.transNavInMsg.subscribeTo(depNav.transOutMsg) # chiefAttRefData.celBodyInMsg.subscribeTo(ephemConverter.ephemOutMsgs[-1]) # We shouldn't need this because the planet is the origin - #chiefSc.attRefInMsg.subscribeTo(chiefAttRefData.attRefOutMsg) # Force the chief spacecraft to follow the hill direction + # chiefSc.attRefInMsg.subscribeTo(chiefAttRefData.attRefOutMsg) # Force the chief spacecraft to follow the hill direction # hillStateConverter hillStateNavObj = hillStateConverter.hillStateConverter() @@ -278,7 +377,7 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us # hillToAtt guidance law w/ static gain depAttRef = hillToAttRef.hillToAttRef() - depAttRef.ModelTag = 'dep_att_ref' + depAttRef.ModelTag = "dep_att_ref" depAttRef.gainMatrix = hillToAttRef.MultiArray(lqr_gain_set) # Configure parameters common to relative attitude guidance modules depAttRef.hillStateInMsg.subscribeTo(hillStateNavObj.hillStateOutMsg) @@ -289,14 +388,13 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us # Set the deputy spacecraft to directly follow the attRefMessage depSc.attRefInMsg.subscribeTo(depAttRef.attRefOutMsg) - scSim.AddModelToTask(dynTaskName, chiefAttRef, 710) - scSim.AddModelToTask(dynTaskName, hillStateNavObj,790) - scSim.AddModelToTask(dynTaskName, depHillRef,789) + scSim.AddModelToTask(dynTaskName, hillStateNavObj, 790) + scSim.AddModelToTask(dynTaskName, depHillRef, 789) scSim.AddModelToTask(dynTaskName, depAttRef, 700) # ----- log ----- # - orbit_period = 2*np.pi/np.sqrt(mu/chief_oe.a**3) - simulationTime = 40*orbit_period#106920.14366466808 + orbit_period = 2 * np.pi / np.sqrt(mu / chief_oe.a**3) + simulationTime = 40 * orbit_period # 106920.14366466808 simulationTime = macros.sec2nano(simulationTime) numDataPoints = 21384 samplingTime = simulationTime // (numDataPoints - 1) @@ -322,22 +420,25 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us scSim.AddModelToTask(dynTaskName, chiefDragForceLog, 705) scSim.AddModelToTask(dynTaskName, depDragForceLog, 706) - for ind,rec in enumerate(atmoRecs): - scSim.AddModelToTask(dynTaskName, rec, 707+ind) + for ind, rec in enumerate(atmoRecs): + scSim.AddModelToTask(dynTaskName, rec, 707 + ind) # if this scenario is to interface with the BSK Viz, uncomment the following lines # to save the BSK data to a file, uncomment the saveFile line below - viz = vizSupport.enableUnityVisualization(scSim, dynTaskName, [chiefSc, depSc], - # saveFile=fileName, - ) + viz = vizSupport.enableUnityVisualization( + scSim, + dynTaskName, + [chiefSc, depSc], + # saveFile=fileName, + ) # ----- execute sim ----- # scSim.InitializeSimulation() scSim.ConfigureStopTime(simulationTime) setupTimeStamp = time.time() - setupTime = setupTimeStamp-startTime + setupTime = setupTimeStamp - startTime print(f"Sim setup complete in {setupTime} seconds, executing...") - #scSim.ShowExecutionFigure(True) + # scSim.ShowExecutionFigure(True) scSim.ExecuteSimulation() execTimeStamp = time.time() @@ -345,22 +446,22 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us print(f"Sim complete in {execTime} seconds, pulling data...") # ----- pull ----- # results_dict = {} - results_dict['chiefDrag_B'] = chiefDragForceLog.forceExternal_B - results_dict['depDrag_B'] = depDragForceLog.forceExternal_B - results_dict['dynTimeData'] = chiefStateRec.times() - results_dict['fswTimeData'] = depAttRec.times() - results_dict['wiggum.r_BN_N'] = chiefStateRec.r_BN_N - results_dict['wiggum.v_BN_N'] = chiefStateRec.v_BN_N - results_dict['lou.r_BN_N'] = depStateRec.r_BN_N - results_dict['lou.v_BN_N'] = depStateRec.v_BN_N - results_dict['relState.r_DC_H'] = hillStateRec.r_DC_H - results_dict['relState.v_DC_H'] = hillStateRec.v_DC_H - results_dict['wiggum.sigma_BN'] = chiefStateRec.sigma_BN - results_dict['lou.sigma_BN'] = depStateRec.sigma_BN - results_dict['depDensity'] = atmoRecs[0].neutralDensity - results_dict['chiefDensity'] = atmoRecs[1].neutralDensity - results_dict['mu'] = mu - results_dict['dens_mult'] = densMultiplier + results_dict["chiefDrag_B"] = chiefDragForceLog.forceExternal_B + results_dict["depDrag_B"] = depDragForceLog.forceExternal_B + results_dict["dynTimeData"] = chiefStateRec.times() + results_dict["fswTimeData"] = depAttRec.times() + results_dict["wiggum.r_BN_N"] = chiefStateRec.r_BN_N + results_dict["wiggum.v_BN_N"] = chiefStateRec.v_BN_N + results_dict["lou.r_BN_N"] = depStateRec.r_BN_N + results_dict["lou.v_BN_N"] = depStateRec.v_BN_N + results_dict["relState.r_DC_H"] = hillStateRec.r_DC_H + results_dict["relState.v_DC_H"] = hillStateRec.v_DC_H + results_dict["wiggum.sigma_BN"] = chiefStateRec.sigma_BN + results_dict["lou.sigma_BN"] = depStateRec.sigma_BN + results_dict["depDensity"] = atmoRecs[0].neutralDensity + results_dict["chiefDensity"] = atmoRecs[1].neutralDensity + results_dict["mu"] = mu + results_dict["dens_mult"] = densMultiplier pullTimeStamp = time.time() pullTime = pullTimeStamp - execTimeStamp overallTime = pullTimeStamp - startTime @@ -368,130 +469,133 @@ def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', us return results_dict -def run(show_plots, altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', useJ2=False): - results = drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType=ctrlType, useJ2=useJ2) - - timeData = results['dynTimeData'] - fswTimeData = results['fswTimeData'] - pos = results['wiggum.r_BN_N'] - vel = results['wiggum.v_BN_N'] - chiefAtt = results['wiggum.sigma_BN'] - pos2 = results['lou.r_BN_N'] - vel2= results['lou.v_BN_N'] - depAtt = results['lou.sigma_BN'] - hillPos = results['relState.r_DC_H'] - hillVel = results['relState.v_DC_H'] - depDensity = results['depDensity'] - chiefDensity = results['chiefDensity'] - depDrag = results['depDrag_B'] - chiefDrag = results['chiefDrag_B'] +def run( + show_plots, altOffset, trueAnomOffset, densMultiplier, ctrlType="lqr", useJ2=False +): + results = drag_simulator( + altOffset, trueAnomOffset, densMultiplier, ctrlType=ctrlType, useJ2=useJ2 + ) + + timeData = results["dynTimeData"] + fswTimeData = results["fswTimeData"] + pos = results["wiggum.r_BN_N"] + vel = results["wiggum.v_BN_N"] + chiefAtt = results["wiggum.sigma_BN"] + pos2 = results["lou.r_BN_N"] + vel2 = results["lou.v_BN_N"] + depAtt = results["lou.sigma_BN"] + hillPos = results["relState.r_DC_H"] + hillVel = results["relState.v_DC_H"] + depDensity = results["depDensity"] + chiefDensity = results["chiefDensity"] + depDrag = results["depDrag_B"] + chiefDrag = results["chiefDrag_B"] densData = results numDataPoints = len(timeData) - mu = results['mu'] - rel_mrp_hist = np.empty([numDataPoints,3]) + mu = results["mu"] + rel_mrp_hist = np.empty([numDataPoints, 3]) - for ind in range(0,numDataPoints): - rel_mrp_hist[ind,:] = rbk.subMRP(depAtt[ind,:], chiefAtt[ind,:]) + for ind in range(0, numDataPoints): + rel_mrp_hist[ind, :] = rbk.subMRP(depAtt[ind, :], chiefAtt[ind, :]) figureList = {} # Plots for general consumption plt.figure() - plt.plot(timeData[1:], hillPos[1:,0],label="r_1") + plt.plot(timeData[1:], hillPos[1:, 0], label="r_1") plt.grid() - plt.xlabel('Time') - plt.ylabel('Hill X Position (m)') + plt.xlabel("Time") + plt.ylabel("Hill X Position (m)") pltName = fileName + "_hillX" figureList[pltName] = plt.figure(1) plt.figure() - plt.plot(timeData[1:], hillPos[1:,1],label="r_2") + plt.plot(timeData[1:], hillPos[1:, 1], label="r_2") plt.grid() - plt.xlabel('Time') - plt.ylabel('Hill Y Position (m)') + plt.xlabel("Time") + plt.ylabel("Hill Y Position (m)") pltName = fileName + "_hillY" figureList[pltName] = plt.figure(2) - plt.figure() - plt.plot(timeData[1:], hillVel[1:,0],label="v_1") + plt.plot(timeData[1:], hillVel[1:, 0], label="v_1") plt.grid() - plt.xlabel('Time') - plt.ylabel('Hill X Velocity (m/s)') + plt.xlabel("Time") + plt.ylabel("Hill X Velocity (m/s)") pltName = fileName + "_hilldX" figureList[pltName] = plt.figure(3) plt.figure() - plt.plot(timeData[1:], hillVel[1:,1],label="v_2") - plt.ylabel('Hill Y Velocity (m/s)') + plt.plot(timeData[1:], hillVel[1:, 1], label="v_2") + plt.ylabel("Hill Y Velocity (m/s)") pltName = fileName + "_hilldy" figureList[pltName] = plt.figure(4) plt.figure() - plt.semilogy(timeData[1:], chiefDensity[1:],label=r'Chief $\rho$') - plt.semilogy(timeData[1:], depDensity[1:],label=r'Deputy $\rho$') + plt.semilogy(timeData[1:], chiefDensity[1:], label=r"Chief $\rho$") + plt.semilogy(timeData[1:], depDensity[1:], label=r"Deputy $\rho$") plt.grid() plt.legend() - plt.xlabel('Time') - plt.ylabel('Density (kg/m3)') + plt.xlabel("Time") + plt.ylabel("Density (kg/m3)") pltName = fileName + "_densities" figureList[pltName] = plt.figure(5) plt.figure() - plt.plot(hillPos[1:,0],hillPos[1:,1]) + plt.plot(hillPos[1:, 0], hillPos[1:, 1]) plt.grid() - plt.xlabel('Hill X (m)') - plt.ylabel('Hill Y (m)') + plt.xlabel("Hill X (m)") + plt.ylabel("Hill Y (m)") pltName = fileName + "_hillTraj" figureList[pltName] = plt.figure(6) plt.figure() plt.plot(timeData, rel_mrp_hist) plt.grid() - plt.xlabel('Time') - plt.ylabel('Relative MRP Value') + plt.xlabel("Time") + plt.ylabel("Relative MRP Value") pltName = fileName + "_relativeAtt" figureList[pltName] = plt.figure(7) # Debug plots plt.figure() - plt.plot(timeData[1:], depDrag[1:,0]-chiefDrag[1:,0],label="delta a_1") - plt.plot(timeData[1:], depDrag[1:,1]-chiefDrag[1:,1],label="delta a_2") - plt.plot(timeData[1:], depDrag[1:,2]-chiefDrag[1:,2],label="delta a_3") + plt.plot(timeData[1:], depDrag[1:, 0] - chiefDrag[1:, 0], label="delta a_1") + plt.plot(timeData[1:], depDrag[1:, 1] - chiefDrag[1:, 1], label="delta a_2") + plt.plot(timeData[1:], depDrag[1:, 2] - chiefDrag[1:, 2], label="delta a_3") plt.grid() plt.legend() - plt.xlabel('Time') - plt.ylabel('Relative acceleration due to drag, body frame (m/s)') + plt.xlabel("Time") + plt.ylabel("Relative acceleration due to drag, body frame (m/s)") plt.figure() - plt.plot(timeData[1:], chiefDrag[1:,0],label="chief a_1") - plt.plot(timeData[1:], chiefDrag[1:,1],label="chief a_2") - plt.plot(timeData[1:], chiefDrag[1:,2],label="chief a_3") + plt.plot(timeData[1:], chiefDrag[1:, 0], label="chief a_1") + plt.plot(timeData[1:], chiefDrag[1:, 1], label="chief a_2") + plt.plot(timeData[1:], chiefDrag[1:, 2], label="chief a_3") plt.grid() plt.legend() - plt.xlabel('Time') - plt.ylabel('Relative acceleration due to drag, body frame (m/s)') + plt.xlabel("Time") + plt.ylabel("Relative acceleration due to drag, body frame (m/s)") plt.figure() - plt.plot(timeData[1:], depDrag[1:,0],label="dep a_1") - plt.plot(timeData[1:], depDrag[1:,1],label="dep a_2") - plt.plot(timeData[1:], depDrag[1:,2],label="dep a_3") + plt.plot(timeData[1:], depDrag[1:, 0], label="dep a_1") + plt.plot(timeData[1:], depDrag[1:, 1], label="dep a_2") + plt.plot(timeData[1:], depDrag[1:, 2], label="dep a_3") plt.grid() plt.legend() - plt.xlabel('Time') - plt.ylabel('Relative acceleration due to drag, body frame (m/s)') + plt.xlabel("Time") + plt.ylabel("Relative acceleration due to drag, body frame (m/s)") - - if(show_plots): + if show_plots: plt.show() plt.close("all") return figureList + if __name__ == "__main__": run( True, # show_plots - 0.0, # altitude offset (m) - 0.1, # True anomaly offset (deg) - 1, # Density multiplier (nondimensional) - ctrlType='lqr', - useJ2=False + 0.0, # altitude offset (m) + 0.1, # True anomaly offset (deg) + 1, # Density multiplier (nondimensional) + ctrlType="lqr", + useJ2=False, ) diff --git a/examples/scenarioExtendingBoom.py b/examples/scenarioExtendingBoom.py index e102ab1a5b..2dbc7edf64 100644 --- a/examples/scenarioExtendingBoom.py +++ b/examples/scenarioExtendingBoom.py @@ -114,7 +114,7 @@ def run(show_plots): gravFactory.addBodiesTo(scObject) timeInitString = "2016 JUNE 3 01:34:30.0" - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', timeInitString, epochInMsg=True) + gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) gravFactory.spiceObject.zeroBase = 'earth' scSim.AddModelToTask(dynTaskName, gravFactory.spiceObject) diff --git a/examples/scenarioFlexiblePanel.py b/examples/scenarioFlexiblePanel.py index 1181ac9094..3762ad12a5 100644 --- a/examples/scenarioFlexiblePanel.py +++ b/examples/scenarioFlexiblePanel.py @@ -83,9 +83,22 @@ import matplotlib.pyplot as plt import numpy as np -from Basilisk.utilities import (SimulationBaseClass, vizSupport, simIncludeGravBody, macros, orbitalMotion, - unitTestSupport, RigidBodyKinematics as rbk) -from Basilisk.simulation import spacecraft, spinningBodyNDOFStateEffector, simpleNav, extForceTorque, svIntegrators +from Basilisk.utilities import ( + SimulationBaseClass, + vizSupport, + simIncludeGravBody, + macros, + orbitalMotion, + unitTestSupport, + RigidBodyKinematics as rbk, +) +from Basilisk.simulation import ( + spacecraft, + spinningBodyNDOFStateEffector, + simpleNav, + extForceTorque, + svIntegrators, +) from Basilisk.fswAlgorithms import mrpFeedback, inertial3D, attTrackingError from Basilisk.architecture import messaging @@ -100,7 +113,9 @@ def run(show_plots, numberOfSegments): scGeometry = geometryClass(numberOfSegments) scObject, panel, extForceTorque = setUpDynModules(scSim, scGeometry) - thetaData, attErrorLog = setUpFSWModules(scSim, scObject, panel, scGeometry, extForceTorque) + thetaData, attErrorLog = setUpFSWModules( + scSim, scObject, panel, scGeometry, extForceTorque + ) if vizSupport.vizFound: setUpVizard(scSim, scObject, panel, scGeometry) @@ -113,7 +128,7 @@ def run(show_plots, numberOfSegments): def createSimBaseClass(): scSim = SimulationBaseClass.SimBaseClass() - scSim.simulationTime = macros.min2nano(10.) + scSim.simulationTime = macros.min2nano(10.0) scSim.dynTaskName = "dynTask" scSim.dynProcess = scSim.CreateNewProcess("dynProcess") simTimeStep = macros.sec2nano(0.1) @@ -159,9 +174,29 @@ def setUpSpacecraft(scSim, scGeometry): scObject = spacecraft.Spacecraft() scObject.ModelTag = "scObject" scObject.hub.mHub = scGeometry.massHub - scObject.hub.IHubPntBc_B = [[scGeometry.massHub / 12 * (scGeometry.lengthHub ** 2 + scGeometry.heightHub ** 2), 0.0, 0.0], - [0.0, scGeometry.massHub / 12 * (scGeometry.widthHub ** 2 + scGeometry.heightHub ** 2), 0.0], - [0.0, 0.0, scGeometry.massHub / 12 * (scGeometry.lengthHub ** 2 + scGeometry.widthHub ** 2)]] + scObject.hub.IHubPntBc_B = [ + [ + scGeometry.massHub + / 12 + * (scGeometry.lengthHub**2 + scGeometry.heightHub**2), + 0.0, + 0.0, + ], + [ + 0.0, + scGeometry.massHub + / 12 + * (scGeometry.widthHub**2 + scGeometry.heightHub**2), + 0.0, + ], + [ + 0.0, + 0.0, + scGeometry.massHub + / 12 + * (scGeometry.lengthHub**2 + scGeometry.widthHub**2), + ], + ] scSim.AddModelToTask(scSim.dynTaskName, scObject) return scObject @@ -173,15 +208,17 @@ def setUpFlexiblePanel(scSim, scObject, scGeometry): for idx in range(scGeometry.numberOfSegments): spinningBody = spinningBodyNDOFStateEffector.SpinningBody() spinningBody.setMass(0.0) - spinningBody.setISPntSc_S([[0.0, 0.0, 0.0], - [0.0, 0.0, 0.0], - [0.0, 0.0, 0.0]]) - spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], - [0.0, 0.0, 1.0]]) + spinningBody.setISPntSc_S([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) + spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) spinningBody.setR_ScS_S([[0.0], [scGeometry.lengthSubPanel / 2], [0.0]]) if idx == 0: - spinningBody.setR_SP_P([[0.0], [scGeometry.lengthHub / 2], [scGeometry.heightHub / 2 - scGeometry.thicknessSubPanel / 2]]) + spinningBody.setR_SP_P( + [ + [0.0], + [scGeometry.lengthHub / 2], + [scGeometry.heightHub / 2 - scGeometry.thicknessSubPanel / 2], + ] + ) else: spinningBody.setR_SP_P([[0.0], [scGeometry.lengthSubPanel], 0.0]) spinningBody.setSHat_S([[1], [0], [0]]) @@ -193,12 +230,32 @@ def setUpFlexiblePanel(scSim, scObject, scGeometry): spinningBody = spinningBodyNDOFStateEffector.SpinningBody() spinningBody.setMass(scGeometry.massSubPanel) - spinningBody.setISPntSc_S([[scGeometry.massSubPanel / 12 * (scGeometry.lengthSubPanel ** 2 + scGeometry.thicknessSubPanel ** 2), 0.0, 0.0], - [0.0, scGeometry.massSubPanel / 12 * (scGeometry.widthSubPanel ** 2 + scGeometry.thicknessSubPanel ** 2), 0.0], - [0.0, 0.0, scGeometry.massSubPanel / 12 * (scGeometry.widthSubPanel ** 2 + scGeometry.lengthSubPanel ** 2)]]) - spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], - [0.0, 0.0, 1.0]]) + spinningBody.setISPntSc_S( + [ + [ + scGeometry.massSubPanel + / 12 + * (scGeometry.lengthSubPanel**2 + scGeometry.thicknessSubPanel**2), + 0.0, + 0.0, + ], + [ + 0.0, + scGeometry.massSubPanel + / 12 + * (scGeometry.widthSubPanel**2 + scGeometry.thicknessSubPanel**2), + 0.0, + ], + [ + 0.0, + 0.0, + scGeometry.massSubPanel + / 12 + * (scGeometry.widthSubPanel**2 + scGeometry.lengthSubPanel**2), + ], + ] + ) + spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) spinningBody.setR_ScS_S([[0.0], [scGeometry.lengthSubPanel / 2], [0.0]]) spinningBody.setR_SP_P([[0.0], [0.0], [0.0]]) spinningBody.setSHat_S([[0], [1], [0]]) @@ -224,16 +281,14 @@ def setUpExtForceTorque(scSim, scObject): def setUpGravity(scSim, scObject): gravFactory = simIncludeGravBody.gravBodyFactory() - gravBodies = gravFactory.createBodies(['earth', 'sun']) - gravBodies['earth'].isCentralBody = True - mu = gravBodies['earth'].mu + gravBodies = gravFactory.createBodies(["earth", "sun"]) + gravBodies["earth"].isCentralBody = True + mu = gravBodies["earth"].mu gravFactory.addBodiesTo(scObject) timeInitString = "2012 MAY 1 00:28:30.0" - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, - epochInMsg=True) - gravFactory.spiceObject.zeroBase = 'earth' + gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) + gravFactory.spiceObject.zeroBase = "earth" scSim.AddModelToTask(scSim.dynTaskName, gravFactory.spiceObject) return mu @@ -260,7 +315,9 @@ def setUpFSWModules(scSim, scObject, panel, scGeometry, extFTObject): attError = setUpGuidance(scSim, sNavObject, inertial3DObj) setUpControl(scSim, extFTObject, attError, scGeometry) - thetaData, attErrorLog = setUpRecorders(scSim, scObject, panel, attError, scGeometry) + thetaData, attErrorLog = setUpRecorders( + scSim, scObject, panel, attError, scGeometry + ) return thetaData, attErrorLog @@ -294,17 +351,68 @@ def setUpGuidance(scSim, sNavObject, inertial3DObj): def setUpControl(scSim, extFTObject, attError, scGeometry): - IHubPntBc_B = np.array([[scGeometry.massHub / 12 * (scGeometry.lengthHub ** 2 + scGeometry.heightHub ** 2), 0.0, 0.0], - [0.0, scGeometry.massHub / 12 * (scGeometry.widthHub ** 2 + scGeometry.heightHub ** 2), 0.0], - [0.0, 0.0, scGeometry.massHub / 12 * (scGeometry.lengthHub ** 2 + scGeometry.widthHub ** 2)]]) + IHubPntBc_B = np.array( + [ + [ + scGeometry.massHub + / 12 + * (scGeometry.lengthHub**2 + scGeometry.heightHub**2), + 0.0, + 0.0, + ], + [ + 0.0, + scGeometry.massHub + / 12 + * (scGeometry.widthHub**2 + scGeometry.heightHub**2), + 0.0, + ], + [ + 0.0, + 0.0, + scGeometry.massHub + / 12 + * (scGeometry.lengthHub**2 + scGeometry.widthHub**2), + ], + ] + ) IPanelPntSc_B = np.array( - [[scGeometry.massPanel / 12 * (scGeometry.lengthPanel ** 2 + scGeometry.thicknessPanel ** 2), 0.0, 0.0], - [0.0, scGeometry.massPanel / 12 * (scGeometry.widthPanel ** 2 + scGeometry.thicknessPanel ** 2), 0.0], - [0.0, 0.0, scGeometry.massPanel / 12 * (scGeometry.widthPanel ** 2 + scGeometry.lengthPanel ** 2)]]) - r_ScB_B = [0.0, scGeometry.lengthHub / 2 + scGeometry.lengthPanel / 2, - scGeometry.heightHub / 2 - scGeometry.thicknessSubPanel / 2] - IHubPntB_B = IHubPntBc_B + IPanelPntSc_B - scGeometry.massPanel * np.array(rbk.v3Tilde(r_ScB_B)) @ np.array( - rbk.v3Tilde(r_ScB_B)) + [ + [ + scGeometry.massPanel + / 12 + * (scGeometry.lengthPanel**2 + scGeometry.thicknessPanel**2), + 0.0, + 0.0, + ], + [ + 0.0, + scGeometry.massPanel + / 12 + * (scGeometry.widthPanel**2 + scGeometry.thicknessPanel**2), + 0.0, + ], + [ + 0.0, + 0.0, + scGeometry.massPanel + / 12 + * (scGeometry.widthPanel**2 + scGeometry.lengthPanel**2), + ], + ] + ) + r_ScB_B = [ + 0.0, + scGeometry.lengthHub / 2 + scGeometry.lengthPanel / 2, + scGeometry.heightHub / 2 - scGeometry.thicknessSubPanel / 2, + ] + IHubPntB_B = ( + IHubPntBc_B + + IPanelPntSc_B + - scGeometry.massPanel + * np.array(rbk.v3Tilde(r_ScB_B)) + @ np.array(rbk.v3Tilde(r_ScB_B)) + ) mrpControl = mrpFeedback.mrpFeedback() mrpControl.ModelTag = "mrpFeedback" @@ -314,7 +422,9 @@ def setUpControl(scSim, extFTObject, attError, scGeometry): mrpControl.P = 2 * np.max(IHubPntB_B) / decayTime mrpControl.K = (mrpControl.P / xi) ** 2 / np.max(IHubPntB_B) - configData = messaging.VehicleConfigMsgPayload(IHubPntB_B=list(IHubPntB_B.flatten())) + configData = messaging.VehicleConfigMsgPayload( + IHubPntB_B=list(IHubPntB_B.flatten()) + ) configDataMsg = messaging.VehicleConfigMsg() configDataMsg.write(configData) @@ -342,20 +452,33 @@ def setUpRecorders(scSim, scObject, panel, attError, scGeometry): def setUpVizard(scSim, scObject, panel, scGeometry): scBodyList = [scObject] for idx in range(scGeometry.numberOfSegments): - scBodyList.append([f"subPanel{idx + 1}", panel.spinningBodyConfigLogOutMsgs[2 * idx + 1]]) - viz = vizSupport.enableUnityVisualization(scSim, scSim.dynTaskName, scBodyList - # , saveFile=fileName - ) - vizSupport.createCustomModel(viz - , simBodiesToModify=[scObject.ModelTag] - , modelPath="CUBE" - , color=vizSupport.toRGBA255("gold") - , scale=[scGeometry.widthHub, scGeometry.lengthHub, scGeometry.heightHub]) + scBodyList.append( + [f"subPanel{idx + 1}", panel.spinningBodyConfigLogOutMsgs[2 * idx + 1]] + ) + viz = vizSupport.enableUnityVisualization( + scSim, + scSim.dynTaskName, + scBodyList, + # , saveFile=fileName + ) + vizSupport.createCustomModel( + viz, + simBodiesToModify=[scObject.ModelTag], + modelPath="CUBE", + color=vizSupport.toRGBA255("gold"), + scale=[scGeometry.widthHub, scGeometry.lengthHub, scGeometry.heightHub], + ) for idx in range(scGeometry.numberOfSegments): - vizSupport.createCustomModel(viz - , simBodiesToModify=[f"subPanel{idx + 1}"] - , modelPath="CUBE" - , scale=[scGeometry.widthSubPanel, scGeometry.lengthSubPanel, scGeometry.thicknessSubPanel]) + vizSupport.createCustomModel( + viz, + simBodiesToModify=[f"subPanel{idx + 1}"], + modelPath="CUBE", + scale=[ + scGeometry.widthSubPanel, + scGeometry.lengthSubPanel, + scGeometry.thicknessSubPanel, + ], + ) viz.settings.orbitLinesOn = -1 @@ -383,11 +506,15 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): plt.clf() ax = plt.axes() for idx in range(scGeometry.numberOfSegments): - plt.plot(timeSecDyn, macros.R2D * theta[2 * idx], label=r'$\theta_' + str(idx + 1) + '$') - plt.legend(fontsize='14') - plt.title('Bending Angles', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\theta$ [deg]', fontsize='18') + plt.plot( + timeSecDyn, + macros.R2D * theta[2 * idx], + label=r"$\theta_" + str(idx + 1) + "$", + ) + plt.legend(fontsize="14") + plt.title("Bending Angles", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\theta$ [deg]", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -398,11 +525,15 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): plt.clf() ax = plt.axes() for idx in range(scGeometry.numberOfSegments): - plt.plot(timeSecDyn, macros.R2D * theta[2 * idx + 1], label=r'$\beta_' + str(idx + 1) + '$') - plt.legend(fontsize='14') - plt.title('Torsional Angles', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\beta$ [deg]', fontsize='18') + plt.plot( + timeSecDyn, + macros.R2D * theta[2 * idx + 1], + label=r"$\beta_" + str(idx + 1) + "$", + ) + plt.legend(fontsize="14") + plt.title("Torsional Angles", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\beta$ [deg]", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -413,11 +544,15 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): plt.clf() ax = plt.axes() for idx in range(scGeometry.numberOfSegments): - plt.plot(timeSecDyn, macros.R2D * thetaDot[2 * idx], label=r'$\dot{\theta}_' + str(idx + 1) + '$') - plt.legend(fontsize='14') - plt.title('Bending Angle Rates', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\dot{\theta}$ [deg/s]', fontsize='18') + plt.plot( + timeSecDyn, + macros.R2D * thetaDot[2 * idx], + label=r"$\dot{\theta}_" + str(idx + 1) + "$", + ) + plt.legend(fontsize="14") + plt.title("Bending Angle Rates", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\dot{\theta}$ [deg/s]", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -428,11 +563,15 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): plt.clf() ax = plt.axes() for idx in range(scGeometry.numberOfSegments): - plt.plot(timeSecDyn, macros.R2D * thetaDot[2 * idx + 1], label=r'$\dot{\beta}_' + str(idx + 1) + '$') - plt.legend(fontsize='14') - plt.title('Torsional Angle Rates', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\dot{\beta}$ [deg/s]', fontsize='18') + plt.plot( + timeSecDyn, + macros.R2D * thetaDot[2 * idx + 1], + label=r"$\dot{\beta}_" + str(idx + 1) + "$", + ) + plt.legend(fontsize="14") + plt.title("Torsional Angle Rates", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\dot{\beta}$ [deg/s]", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -443,13 +582,16 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): plt.clf() ax = plt.axes() for idx in range(3): - plt.plot(timeSecFSW, attErrorLog.sigma_BR[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\sigma_' + str(idx) + '$') - plt.legend(fontsize='14') - plt.title('Attitude Error', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\sigma_{B/R}$', fontsize='18') + plt.plot( + timeSecFSW, + attErrorLog.sigma_BR[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\sigma_" + str(idx) + "$", + ) + plt.legend(fontsize="14") + plt.title("Attitude Error", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\sigma_{B/R}$", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -460,13 +602,16 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): plt.clf() ax = plt.axes() for idx in range(3): - plt.plot(timeSecFSW, attErrorLog.omega_BR_B[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\omega_' + str(idx) + '$') - plt.legend(fontsize='14') - plt.title('Attitude Error Rate', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\omega_{B/R}$', fontsize='18') + plt.plot( + timeSecFSW, + attErrorLog.omega_BR_B[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\omega_" + str(idx) + "$", + ) + plt.legend(fontsize="14") + plt.title("Attitude Error Rate", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\omega_{B/R}$", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -482,7 +627,4 @@ def plotting(show_plots, thetaData, attErrorLog, scGeometry): if __name__ == "__main__": - run( - True, - 5 - ) + run(True, 5) diff --git a/examples/scenarioFormationMeanOEFeedback.py b/examples/scenarioFormationMeanOEFeedback.py index 3ce6c74ba1..0fda55931b 100644 --- a/examples/scenarioFormationMeanOEFeedback.py +++ b/examples/scenarioFormationMeanOEFeedback.py @@ -65,7 +65,6 @@ """ - import math import os @@ -83,6 +82,7 @@ from Basilisk.utilities import unitTestSupport from Basilisk.utilities import vizSupport from Basilisk.architecture import astroConstants +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] fileName = os.path.basename(os.path.splitext(__file__)[0]) @@ -113,9 +113,7 @@ def run(show_plots, useClassicElem, numOrbits): scObject.ModelTag = "scObject" scObject2.ModelTag = "scObject2" - I = [900., 0., 0., - 0., 800., 0., - 0., 0., 600.] + I = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0] scObject.hub.mHub = 500.0 scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) @@ -131,7 +129,8 @@ def run(show_plots, useClassicElem, numOrbits): earth = gravFactory.createEarth() earth.isCentralBody = True mu = earth.mu - earth.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S.txt', 2) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + earth.useSphericalHarmonicsGravityModel(str(ggm03s_path), 2) gravFactory.addBodiesTo(scObject) gravFactory.addBodiesTo(scObject2) @@ -162,20 +161,52 @@ def run(show_plots, useClassicElem, numOrbits): meanOEFeedbackObj.chiefTransInMsg.subscribeTo(simpleNavObject.transOutMsg) meanOEFeedbackObj.deputyTransInMsg.subscribeTo(simpleNavObject2.transOutMsg) extFTObject2.cmdForceInertialInMsg.subscribeTo(meanOEFeedbackObj.forceOutMsg) - meanOEFeedbackObj.K = [1e7, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 1e7, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 1e7, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 1e7, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 1e7, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 1e7] + meanOEFeedbackObj.K = [ + 1e7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1e7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1e7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1e7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1e7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1e7, + ] meanOEFeedbackObj.targetDiffOeMean = [0.000, 0.000, 0.000, 0.0003, 0.0002, 0.0001] if useClassicElem: meanOEFeedbackObj.oeType = 0 # 0: classic else: meanOEFeedbackObj.oeType = 1 # 1: equinoctial - meanOEFeedbackObj.mu = astroConstants.MU_EARTH*1e9 # [m^3/s^2] - meanOEFeedbackObj.req = astroConstants.REQ_EARTH*1e3 # [m] - meanOEFeedbackObj.J2 = astroConstants.J2_EARTH # [] + meanOEFeedbackObj.mu = astroConstants.MU_EARTH * 1e9 # [m^3/s^2] + meanOEFeedbackObj.req = astroConstants.REQ_EARTH * 1e3 # [m] + meanOEFeedbackObj.J2 = astroConstants.J2_EARTH # [] scSim.AddModelToTask(fswTaskName, meanOEFeedbackObj, 1) # ----- Setup spacecraft initial states ----- # @@ -194,7 +225,7 @@ def run(show_plots, useClassicElem, numOrbits): scObject.hub.v_CN_NInit = vN # m/s oe2 = orbitalMotion.ClassicElements() - oe2.a = oe.a*(1 + 0.0001) + oe2.a = oe.a * (1 + 0.0001) oe2.e = oe.e + 0.0002 oe2.i = oe.i - 0.0003 oe2.Omega = oe.Omega + 0.0004 @@ -207,11 +238,13 @@ def run(show_plots, useClassicElem, numOrbits): scObject2.hub.v_CN_NInit = vN2 # m/s # ----- log ----- # - orbit_period = 2*math.pi/math.sqrt(mu/oe.a**3) - simulationTime = orbit_period*numOrbits + orbit_period = 2 * math.pi / math.sqrt(mu / oe.a**3) + simulationTime = orbit_period * numOrbits simulationTime = macros.sec2nano(simulationTime) numDataPoints = 1000 - samplingTime = unitTestSupport.samplingTime(simulationTime, dynTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, dynTimeStep, numDataPoints + ) dataLog = scObject.scStateOutMsg.recorder(samplingTime) dataLog2 = scObject2.scStateOutMsg.recorder(samplingTime) scSim.AddModelToTask(dynTaskName, dataLog) @@ -219,9 +252,12 @@ def run(show_plots, useClassicElem, numOrbits): # if this scenario is to interface with the BSK Viz, uncomment the following lines # to save the BSK data to a file, uncomment the saveFile line below - viz = vizSupport.enableUnityVisualization(scSim, dynTaskName, [scObject, scObject2] - # , saveFile=fileName - ) + viz = vizSupport.enableUnityVisualization( + scSim, + dynTaskName, + [scObject, scObject2], + # , saveFile=fileName + ) # ----- execute sim ----- # scSim.InitializeSimulation() @@ -233,7 +269,7 @@ def run(show_plots, useClassicElem, numOrbits): vel = dataLog.v_BN_N pos2 = dataLog2.r_BN_N vel2 = dataLog2.v_BN_N - timeData = dataLog.times()*macros.NANO2SEC/orbit_period + timeData = dataLog.times() * macros.NANO2SEC / orbit_period # ----- plot ----- # # classical oe (figure1) @@ -243,26 +279,41 @@ def run(show_plots, useClassicElem, numOrbits): # spacecraft 1 (chief) oe_cl_osc = orbitalMotion.rv2elem(mu, pos[i], vel[i]) oe_cl_mean = orbitalMotion.ClassicElements() - orbitalMotion.clMeanOscMap(orbitalMotion.REQ_EARTH*1e3, orbitalMotion.J2_EARTH, oe_cl_osc, oe_cl_mean, -1) + orbitalMotion.clMeanOscMap( + orbitalMotion.REQ_EARTH * 1e3, + orbitalMotion.J2_EARTH, + oe_cl_osc, + oe_cl_mean, + -1, + ) # spacecraft 2 (deputy) oe2_cl_osc = orbitalMotion.rv2elem(mu, pos2[i], vel2[i]) oe2_cl_mean = orbitalMotion.ClassicElements() - orbitalMotion.clMeanOscMap(orbitalMotion.REQ_EARTH*1e3, orbitalMotion.J2_EARTH, oe2_cl_osc, oe2_cl_mean, -1) + orbitalMotion.clMeanOscMap( + orbitalMotion.REQ_EARTH * 1e3, + orbitalMotion.J2_EARTH, + oe2_cl_osc, + oe2_cl_mean, + -1, + ) # calculate oed - oed_cl[i, 0] = (oe2_cl_mean.a - oe_cl_mean.a)/oe_cl_mean.a # delta a (normalized) + oed_cl[i, 0] = ( + oe2_cl_mean.a - oe_cl_mean.a + ) / oe_cl_mean.a # delta a (normalized) oed_cl[i, 1] = oe2_cl_mean.e - oe_cl_mean.e # delta e oed_cl[i, 2] = oe2_cl_mean.i - oe_cl_mean.i # delta i oed_cl[i, 3] = oe2_cl_mean.Omega - oe_cl_mean.Omega # delta Omega oed_cl[i, 4] = oe2_cl_mean.omega - oe_cl_mean.omega # delta omega E_tmp = orbitalMotion.f2E(oe_cl_mean.f, oe_cl_mean.e) E2_tmp = orbitalMotion.f2E(oe2_cl_mean.f, oe2_cl_mean.e) - oed_cl[i, 5] = orbitalMotion.E2M( - E2_tmp, oe2_cl_mean.e) - orbitalMotion.E2M(E_tmp, oe_cl_mean.e) # delta M + oed_cl[i, 5] = orbitalMotion.E2M(E2_tmp, oe2_cl_mean.e) - orbitalMotion.E2M( + E_tmp, oe_cl_mean.e + ) # delta M for j in range(3, 6): - while(oed_cl[i, j] > math.pi): - oed_cl[i, j] = oed_cl[i, j] - 2*math.pi - while(oed_cl[i, j] < -math.pi): - oed_cl[i, j] = oed_cl[i, j] + 2*math.pi + while oed_cl[i, j] > math.pi: + oed_cl[i, j] = oed_cl[i, j] - 2 * math.pi + while oed_cl[i, j] < -math.pi: + oed_cl[i, j] = oed_cl[i, j] + 2 * math.pi plt.plot(timeData, oed_cl[:, 0], label="da") plt.plot(timeData, oed_cl[:, 1], label="de") plt.plot(timeData, oed_cl[:, 2], label="di") @@ -282,26 +333,40 @@ def run(show_plots, useClassicElem, numOrbits): # spacecraft 1 (chief) oe_cl_osc = orbitalMotion.rv2elem(mu, pos[i], vel[i]) oe_cl_mean = orbitalMotion.ClassicElements() - orbitalMotion.clMeanOscMap(orbitalMotion.REQ_EARTH*1e3, orbitalMotion.J2_EARTH, oe_cl_osc, oe_cl_mean, -1) + orbitalMotion.clMeanOscMap( + orbitalMotion.REQ_EARTH * 1e3, + orbitalMotion.J2_EARTH, + oe_cl_osc, + oe_cl_mean, + -1, + ) oe_eq_mean = orbitalMotion.EquinoctialElements() orbitalMotion.clElem2eqElem(oe_cl_mean, oe_eq_mean) # spacecraft 2 (deputy) oe2_cl_osc = orbitalMotion.rv2elem(mu, pos2[i], vel2[i]) oe2_cl_mean = orbitalMotion.ClassicElements() - orbitalMotion.clMeanOscMap(orbitalMotion.REQ_EARTH*1e3, orbitalMotion.J2_EARTH, oe2_cl_osc, oe2_cl_mean, -1) + orbitalMotion.clMeanOscMap( + orbitalMotion.REQ_EARTH * 1e3, + orbitalMotion.J2_EARTH, + oe2_cl_osc, + oe2_cl_mean, + -1, + ) oe2_eq_mean = orbitalMotion.EquinoctialElements() orbitalMotion.clElem2eqElem(oe2_cl_mean, oe2_eq_mean) # calculate oed - oed_eq[i, 0] = (oe2_eq_mean.a - oe_eq_mean.a)/oe_eq_mean.a # delta a (normalized) + oed_eq[i, 0] = ( + oe2_eq_mean.a - oe_eq_mean.a + ) / oe_eq_mean.a # delta a (normalized) oed_eq[i, 1] = oe2_eq_mean.P1 - oe_eq_mean.P1 # delta P1 oed_eq[i, 2] = oe2_eq_mean.P2 - oe_eq_mean.P2 # delta P2 oed_eq[i, 3] = oe2_eq_mean.Q1 - oe_eq_mean.Q1 # delta Q1 oed_eq[i, 4] = oe2_eq_mean.Q2 - oe_eq_mean.Q2 # delta Q2 oed_eq[i, 5] = oe2_eq_mean.l - oe_eq_mean.l # delta l - while(oed_eq[i, 5] > math.pi): - oed_eq[i, 5] = oed_eq[i, 5] - 2*math.pi - while(oed_eq[i, 5] < -math.pi): - oed_eq[i, 5] = oed_eq[i, 5] + 2*math.pi + while oed_eq[i, 5] > math.pi: + oed_eq[i, 5] = oed_eq[i, 5] - 2 * math.pi + while oed_eq[i, 5] < -math.pi: + oed_eq[i, 5] = oed_eq[i, 5] + 2 * math.pi plt.plot(timeData, oed_eq[:, 0], label="da") plt.plot(timeData, oed_eq[:, 1], label="dP1") plt.plot(timeData, oed_eq[:, 2], label="dP2") @@ -314,7 +379,7 @@ def run(show_plots, useClassicElem, numOrbits): pltName = fileName + "2" + str(int(useClassicElem)) figureList[pltName] = plt.figure(2) - if(show_plots): + if show_plots: plt.show() plt.close("all") @@ -325,5 +390,5 @@ def run(show_plots, useClassicElem, numOrbits): run( True, # show_plots True, # useClassicElem - 40 # number of orbits + 40, # number of orbits ) diff --git a/examples/scenarioGroundDownlink.py b/examples/scenarioGroundDownlink.py index 2b124d806f..d3ded992f2 100644 --- a/examples/scenarioGroundDownlink.py +++ b/examples/scenarioGroundDownlink.py @@ -54,6 +54,7 @@ .. image:: /_images/Scenarios/scenarioGroundPassStorage.svg :align: center """ + import inspect import os @@ -62,12 +63,17 @@ filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) -bskName = 'Basilisk' +bskName = "Basilisk" splitPath = path.split(bskName) # Import all of the modules that we are going to be called in this simulation from Basilisk.utilities import SimulationBaseClass -from Basilisk.simulation import simpleInstrument, simpleStorageUnit, partitionedStorageUnit, spaceToGroundTransmitter +from Basilisk.simulation import ( + simpleInstrument, + simpleStorageUnit, + partitionedStorageUnit, + spaceToGroundTransmitter, +) from Basilisk.simulation import groundLocation from Basilisk.utilities import vizSupport from Basilisk.utilities import unitTestSupport @@ -77,12 +83,15 @@ from Basilisk.utilities import orbitalMotion from Basilisk.utilities import simIncludeGravBody from Basilisk.architecture import astroConstants +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile from Basilisk import __path__ + bskPath = __path__[0] path = os.path.dirname(os.path.abspath(__file__)) + def run(show_plots): """ The scenarios can be run with the followings setups parameters: @@ -92,14 +101,14 @@ def run(show_plots): """ - taskName = "unitTask" # arbitrary name (don't change) - processname = "TestProcess" # arbitrary name (don't change) + taskName = "unitTask" # arbitrary name (don't change) + processname = "TestProcess" # arbitrary name (don't change) # Create a sim module as an empty container scenarioSim = SimulationBaseClass.SimBaseClass() # Create test thread - testProcessRate = macros.sec2nano(10.0) # update process rate update time + testProcessRate = macros.sec2nano(10.0) # update process rate update time testProc = scenarioSim.CreateNewProcess(processname) testProc.addTask(scenarioSim.CreateNewTask(taskName, testProcessRate)) @@ -112,28 +121,27 @@ def run(show_plots): gravFactory = simIncludeGravBody.gravBodyFactory() planet = gravFactory.createEarth() planet.isCentralBody = True # ensure this is the central gravitational body - - planet.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S-J2-only.txt', 2) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S_J2_only) + planet.useSphericalHarmonicsGravityModel(str(ggm03s_path), 2) mu = planet.mu # setup Spice interface for some solar system bodies - timeInitString = '2020 MAY 21 18:28:03 (UTC)' + timeInitString = "2020 MAY 21 18:28:03 (UTC)" spiceObject = gravFactory.createSpiceInterface(time=timeInitString) scenarioSim.AddModelToTask(taskName, spiceObject, -1) - # setup orbit using orbitalMotion library oe = orbitalMotion.ClassicElements() - oe.a = astroConstants.REQ_EARTH*1e3 + 418e3 + oe.a = astroConstants.REQ_EARTH * 1e3 + 418e3 oe.e = 0.00061 - oe.i = 51.6418*macros.D2R + oe.i = 51.6418 * macros.D2R - oe.Omega = 119.2314*macros.D2R - oe.omega = 337.8329*macros.D2R - oe.f = 22.2753*macros.D2R + oe.Omega = 119.2314 * macros.D2R + oe.omega = 337.8329 * macros.D2R + oe.f = 22.2753 * macros.D2R rN, vN = orbitalMotion.elem2rv(mu, oe) - n = np.sqrt(mu/oe.a/oe.a/oe.a) - P = 2.*np.pi/n + n = np.sqrt(mu / oe.a / oe.a / oe.a) + P = 2.0 * np.pi / n scObject.hub.r_CN_NInit = rN scObject.hub.v_CN_NInit = vN @@ -148,10 +156,10 @@ def run(show_plots): # Create the ground location groundStation = groundLocation.GroundLocation() groundStation.ModelTag = "BoulderGroundStation" - groundStation.planetRadius = astroConstants.REQ_EARTH*1e3 + groundStation.planetRadius = astroConstants.REQ_EARTH * 1e3 groundStation.specifyLocation(np.radians(40.009971), np.radians(-105.243895), 1624) groundStation.planetInMsg.subscribeTo(spiceObject.planetStateOutMsgs[0]) - groundStation.minimumElevation = np.radians(10.) + groundStation.minimumElevation = np.radians(10.0) groundStation.maximumRange = 1e9 groundStation.addSpacecraftToModel(scObject.scStateOutMsg) scenarioSim.AddModelToTask(taskName, groundStation) @@ -159,22 +167,22 @@ def run(show_plots): # Create an instrument instrument = simpleInstrument.SimpleInstrument() instrument.ModelTag = "instrument1" - instrument.nodeBaudRate = 2400. # baud - instrument.nodeDataName = "Instrument 1" # baud + instrument.nodeBaudRate = 2400.0 # baud + instrument.nodeDataName = "Instrument 1" # baud scenarioSim.AddModelToTask(taskName, instrument) # Create another instrument instrument2 = simpleInstrument.SimpleInstrument() instrument2.ModelTag = "instrument2" - instrument2.nodeBaudRate = 2400. # baud + instrument2.nodeBaudRate = 2400.0 # baud instrument2.nodeDataName = "Instrument 2" # baud scenarioSim.AddModelToTask(taskName, instrument2) # Create a "transmitter" transmitter = spaceToGroundTransmitter.SpaceToGroundTransmitter() transmitter.ModelTag = "transmitter" - transmitter.nodeBaudRate = -9600. # baud - transmitter.packetSize = -1E6 # bits + transmitter.nodeBaudRate = -9600.0 # baud + transmitter.packetSize = -1e6 # bits transmitter.numBuffers = 2 transmitter.addAccessMsgToTransmitter(groundStation.accessOutMsgs[-1]) scenarioSim.AddModelToTask(taskName, transmitter) @@ -182,7 +190,7 @@ def run(show_plots): # Create a partitionedStorageUnit and attach the instrument to it dataMonitor = partitionedStorageUnit.PartitionedStorageUnit() dataMonitor.ModelTag = "dataMonitor" - dataMonitor.storageCapacity = 8E9 # bits (1 GB) + dataMonitor.storageCapacity = 8e9 # bits (1 GB) dataMonitor.addDataNodeToModel(instrument.nodeDataOutMsg) dataMonitor.addDataNodeToModel(instrument2.nodeDataOutMsg) dataMonitor.addDataNodeToModel(transmitter.nodeDataOutMsg) @@ -195,7 +203,7 @@ def run(show_plots): # Create a simpleStorageUnit and attach the instrument to it dataMonitor2 = simpleStorageUnit.SimpleStorageUnit() dataMonitor2.ModelTag = "dataMonitor2" - dataMonitor2.storageCapacity = 1E5 # bits + dataMonitor2.storageCapacity = 1e5 # bits dataMonitor2.addDataNodeToModel(instrument.nodeDataOutMsg) dataMonitor2.addDataNodeToModel(instrument2.nodeDataOutMsg) dataMonitor2.addDataNodeToModel(transmitter.nodeDataOutMsg) @@ -221,17 +229,22 @@ def run(show_plots): # setup Vizard support if vizSupport.vizFound: - viz = vizSupport.enableUnityVisualization(scenarioSim, taskName, scObject - # , saveFile=__file__ - ) - vizSupport.addLocation(viz, stationName="Boulder Station" - , parentBodyName=planet.displayName - , r_GP_P=unitTestSupport.EigenVector3d2list(groundStation.r_LP_P_Init) - , fieldOfView=np.radians(160.) - , color='pink' - , range=1000.0*1000 # meters - , label="Boulder Pink" - ) + viz = vizSupport.enableUnityVisualization( + scenarioSim, + taskName, + scObject, + # , saveFile=__file__ + ) + vizSupport.addLocation( + viz, + stationName="Boulder Station", + parentBodyName=planet.displayName, + r_GP_P=unitTestSupport.EigenVector3d2list(groundStation.r_LP_P_Init), + fieldOfView=np.radians(160.0), + color="pink", + range=1000.0 * 1000, # meters + label="Boulder Pink", + ) viz.settings.spacecraftSizeMultiplier = 1.5 viz.settings.showLocationCommLines = 1 viz.settings.showLocationCones = 1 @@ -244,15 +257,14 @@ def run(show_plots): # NOTE: the total simulation time may be longer than this value. The # simulation is stopped at the next logging event on or after the # simulation end time. - scenarioSim.ConfigureStopTime(macros.hour2nano(12)) # seconds to stop simulation + scenarioSim.ConfigureStopTime(macros.hour2nano(12)) # seconds to stop simulation scenarioSim.ExecuteSimulation() scenarioSim.ConfigureStopTime(macros.hour2nano(24)) if vizSupport.vizFound: - vizSupport.changeLocation(viz, stationName="Boulder Station" - , color="blue" - , label="Boulder Blue" - ) + vizSupport.changeLocation( + viz, stationName="Boulder Station", color="blue", label="Boulder Blue" + ) scenarioSim.ExecuteSimulation() # Grabbed logged data for plotting @@ -280,55 +292,65 @@ def run(show_plots): # Stopped here. Revisiting instrument implementation first. figureList = {} plt.close("all") # clears out plots from earlier test runs - fig=plt.figure(1) - plt.plot(tvec, storageLevel/(8E3), label='Data Unit Total Storage Level (KB)') - plt.plot(tvec, storedData[:, 0]/(8E3), label='Instrument 1 Partition Level (KB)') - plt.plot(tvec, storedData[:, 1]/(8E3), label='Instrument 2 Partition Level (KB)') - plt.xlabel('Time (Hr)') - plt.ylabel('Data Stored (KB)') + fig = plt.figure(1) + plt.plot(tvec, storageLevel / (8e3), label="Data Unit Total Storage Level (KB)") + plt.plot(tvec, storedData[:, 0] / (8e3), label="Instrument 1 Partition Level (KB)") + plt.plot(tvec, storedData[:, 1] / (8e3), label="Instrument 2 Partition Level (KB)") + plt.xlabel("Time (Hr)") + plt.ylabel("Data Stored (KB)") plt.grid(True) plt.legend() - figureList['scenarioGroundPassStorage'] = fig + figureList["scenarioGroundPassStorage"] = fig # Plot the orbit and ground station location data fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.plot(scPosition[:,0]/1000.,scPosition[:, 1]/1000.,scPosition[:,2]/1000., label='S/C Position') - ax.plot(groundPosition[:,0]/1000.,groundPosition[:, 0]/1000.,groundPosition[:,2]/1000., label='Ground Station Position') + ax = fig.add_subplot(1, 1, 1, projection="3d") + ax.plot( + scPosition[:, 0] / 1000.0, + scPosition[:, 1] / 1000.0, + scPosition[:, 2] / 1000.0, + label="S/C Position", + ) + ax.plot( + groundPosition[:, 0] / 1000.0, + groundPosition[:, 0] / 1000.0, + groundPosition[:, 2] / 1000.0, + label="Ground Station Position", + ) plt.legend() - figureList['scenarioGroundPassECI'] = fig + figureList["scenarioGroundPassECI"] = fig fig = plt.figure() - plt.polar(pass_az, 90.-np.degrees(pass_el)) + plt.polar(pass_az, 90.0 - np.degrees(pass_el)) # ax.set_yticks(range(0, 90, 10)) # Define the yticks # ax.set_yticklabels(map(str, range(90, 0, -10))) - plt.title('Ground Pass Azimuth and Declination') - figureList['scenarioGroundPassPolar'] = fig + plt.title("Ground Pass Azimuth and Declination") + figureList["scenarioGroundPassPolar"] = fig plt.figure() - plt.plot(tvec, np.degrees(azimuthData),label='az') - plt.plot(tvec, np.degrees(elevationData), label='el') + plt.plot(tvec, np.degrees(azimuthData), label="az") + plt.plot(tvec, np.degrees(elevationData), label="el") plt.legend() plt.grid(True) - plt.ylabel('Angles (deg)') - plt.xlabel('Time (hr)') + plt.ylabel("Angles (deg)") + plt.xlabel("Time (hr)") - fig=plt.figure() - plt.plot(tvec, rangeData/1000.) - plt.plot(tvec, accessData*1000.) + fig = plt.figure() + plt.plot(tvec, rangeData / 1000.0) + plt.plot(tvec, accessData * 1000.0) plt.grid(True) - plt.title('Slant Range, Access vs. Time') - plt.ylabel('Slant Range (km)') - plt.xlabel('Time (hr)') - figureList['scenarioGroundPassRange'] = fig + plt.title("Slant Range, Access vs. Time") + plt.ylabel("Slant Range (km)") + plt.xlabel("Time (hr)") + figureList["scenarioGroundPassRange"] = fig fig = plt.figure() - plt.plot(tvec,storageNetBaud / (8E3), label='Net Baud Rate (KB/s)') - plt.xlabel('Time (Hr)') - plt.ylabel('Data Rate (KB/s)') + plt.plot(tvec, storageNetBaud / (8e3), label="Net Baud Rate (KB/s)") + plt.xlabel("Time (Hr)") + plt.ylabel("Data Rate (KB/s)") plt.grid(True) plt.legend() - figureList['scenarioGroundPassBaud'] = fig + figureList["scenarioGroundPassBaud"] = fig if show_plots: plt.show() @@ -336,6 +358,7 @@ def run(show_plots): return figureList + # This statement below ensures that the unitTestScript can be run as a # stand-alone python script diff --git a/examples/scenarioHaloOrbit.py b/examples/scenarioHaloOrbit.py index 9f0e2b5dc2..585cd2b9f3 100644 --- a/examples/scenarioHaloOrbit.py +++ b/examples/scenarioHaloOrbit.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2024, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -66,20 +65,30 @@ # import os -from datetime import datetime import matplotlib.pyplot as plt import numpy as np from Basilisk import __path__ from Basilisk.simulation import spacecraft from Basilisk.topLevelModules import pyswice -from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion, - simIncludeGravBody, unitTestSupport, vizSupport) +from Basilisk.utilities import ( + SimulationBaseClass, + macros, + orbitalMotion, + simIncludeGravBody, + unitTestSupport, + vizSupport, +) from Basilisk.utilities.pyswice_spk_utilities import spkRead +from Basilisk.utilities.supportDataTools.dataFetcher import ( + get_path, + DataFile, +) bskPath = __path__[0] fileName = os.path.basename(os.path.splitext(__file__)[0]) + def run(showPlots=True): """ Args: @@ -113,8 +122,8 @@ def run(showPlots=True): # Setup gravity factory and gravity bodies # Include bodies as a list of SPICE names gravFactory = simIncludeGravBody.gravBodyFactory() - gravBodies = gravFactory.createBodies('moon', 'earth') - gravBodies['earth'].isCentralBody = True + gravBodies = gravFactory.createBodies("moon", "earth") + gravBodies["earth"].isCentralBody = True # Add gravity bodies to the spacecraft dynamics gravFactory.addBodiesTo(scObject) @@ -122,37 +131,48 @@ def run(showPlots=True): # Create default SPICE module, specify start date/time. timeInitString = "2022 August 31 15:00:00.0" bsk_path = __path__[0] - spiceObject = gravFactory.createSpiceInterface(bsk_path + "/supportData/EphemerisData/", time=timeInitString, - epochInMsg=True) - spiceObject.zeroBase = 'earth' + spiceObject = gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) + spiceObject.zeroBase = "earth" # Add SPICE object to the simulation task list scSim.AddModelToTask(simTaskName, spiceObject, 1) # Import SPICE ephemeris data into the python environment - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) # solar system bodies + pyswice.furnsh_c(str(naif0012_path)) # leap second file + pyswice.furnsh_c(str(de403masses_path)) # solar system masses + pyswice.furnsh_c(str(pck00010_path)) # generic Planetary Constants Kernel # Set spacecraft ICs # Get initial moon data - moonSpiceName = 'moon' - moonInitialState = 1000 * spkRead(moonSpiceName, timeInitString, 'J2000', 'earth') + moonSpiceName = "moon" + moonInitialState = 1000 * spkRead(moonSpiceName, timeInitString, "J2000", "earth") moon_rN_init = moonInitialState[0:3] moon_vN_init = moonInitialState[3:6] - moon = gravBodies['moon'] - earth = gravBodies['earth'] + moon = gravBodies["moon"] + earth = gravBodies["earth"] oe = orbitalMotion.rv2elem(earth.mu, moon_rN_init, moon_vN_init) moon_a = oe.a # Direction Cosine Matrix (DCM) from earth centered inertial frame to earth-moon rotation frame - DCMInit = np.array([moon_rN_init/np.linalg.norm(moon_rN_init),moon_vN_init/np.linalg.norm(moon_vN_init), - np.cross(moon_rN_init, moon_vN_init) / np.linalg.norm(np.cross(moon_rN_init, moon_vN_init))]) + DCMInit = np.array( + [ + moon_rN_init / np.linalg.norm(moon_rN_init), + moon_vN_init / np.linalg.norm(moon_vN_init), + np.cross(moon_rN_init, moon_vN_init) + / np.linalg.norm(np.cross(moon_rN_init, moon_vN_init)), + ] + ) # Set up non-dimensional parameters - T_ND = np.sqrt(moon_a ** 3 / (earth.mu + moon.mu)) # non-dimensional time for one second - mu_ND = moon.mu/(earth.mu + moon.mu) # non-dimensional mass + T_ND = np.sqrt( + moon_a**3 / (earth.mu + moon.mu) + ) # non-dimensional time for one second + mu_ND = moon.mu / (earth.mu + moon.mu) # non-dimensional mass # Set up initial conditions for the spacecraft x0 = 1.182212 * moon_a + moon_a * mu_ND @@ -172,7 +192,9 @@ def run(showPlots=True): # Setup data logging numDataPoints = 1000 - samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, simulationTimeStep, numDataPoints + ) # Setup spacecraft data recorder scDataRec = scObject.scStateOutMsg.recorder(samplingTime) @@ -181,9 +203,12 @@ def run(showPlots=True): scSim.AddModelToTask(simTaskName, MoonDataRec) if vizSupport.vizFound: - viz = vizSupport.enableUnityVisualization(scSim, simTaskName, scObject, - # saveFile=__file__ - ) + viz = vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + # saveFile=__file__ + ) viz.settings.showCelestialBodyLabels = 1 viz.settings.mainCameraTarget = "earth" viz.settings.trueTrajectoryLinesOn = 4 @@ -213,10 +238,10 @@ def run(showPlots=True): fig = plt.figure(1, figsize=tuple(np.array((1.0, b / oe.a)) * 4.75), dpi=100) plt.axis(np.array([-oe.rApoap, oe.rPeriap, -b, b]) / 1000 * 1.25) ax = fig.gca() - ax.ticklabel_format(style='scientific', scilimits=[-5, 3]) + ax.ticklabel_format(style="scientific", scilimits=[-5, 3]) # Draw 'cartoon' Earth - ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b')) + ax.add_artist(plt.Circle((0, 0), 0.2e5, color="b")) # Plot spacecraft orbit data rDataSpacecraft = [] @@ -225,8 +250,13 @@ def run(showPlots=True): oeDataSpacecraft = orbitalMotion.rv2elem(earth.mu, posData[ii], velData[ii]) rDataSpacecraft.append(oeDataSpacecraft.rmag) fDataSpacecraft.append(oeDataSpacecraft.f + oeDataSpacecraft.omega - oe.omega) - plt.plot(rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, rDataSpacecraft * np.sin(fDataSpacecraft) / 1000, - color='g', linewidth=3.0, label='Spacecraft') + plt.plot( + rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, + rDataSpacecraft * np.sin(fDataSpacecraft) / 1000, + color="g", + linewidth=3.0, + label="Spacecraft", + ) # Plot moon orbit data rDataMoon = [] @@ -235,11 +265,16 @@ def run(showPlots=True): oeDataMoon = orbitalMotion.rv2elem(earth.mu, moonPos[ii], moonVel[ii]) rDataMoon.append(oeDataMoon.rmag) fDataMoon.append(oeDataMoon.f + oeDataMoon.omega - oe.omega) - plt.plot(rDataMoon * np.cos(fDataMoon) / 1000, rDataMoon * np.sin(fDataMoon) / 1000, color='0.5', - linewidth=3.0, label='Moon') + plt.plot( + rDataMoon * np.cos(fDataMoon) / 1000, + rDataMoon * np.sin(fDataMoon) / 1000, + color="0.5", + linewidth=3.0, + label="Moon", + ) - plt.xlabel(r'$i_e$ Coord. [km]') - plt.ylabel(r'$i_p$ Coord. [km]') + plt.xlabel(r"$i_e$ Coord. [km]") + plt.ylabel(r"$i_p$ Coord. [km]") plt.grid() plt.legend() pltName = fileName + "Fig1" @@ -248,12 +283,12 @@ def run(showPlots=True): # Second plot: Draw orbit in frame rotating with the Moon (the center is L2 point) # x axis is moon position vector direction and y axis is moon velocity vector direction fig = plt.figure(2, figsize=tuple(np.array((1.0, b / oe.a)) * 4.75), dpi=100) - plt.axis(np.array([-1e5, 5e5, -3e5, 3e5]) * 1.25) + plt.axis(np.array([-1e5, 5e5, -3e5, 3e5]) * 1.25) ax = fig.gca() - ax.ticklabel_format(style='scientific', scilimits=[-5, 3]) + ax.ticklabel_format(style="scientific", scilimits=[-5, 3]) # Draw 'cartoon' Earth - ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b')) + ax.add_artist(plt.Circle((0, 0), 0.2e5, color="b")) # Plot spacecraft orbit data rSpacecraft = np.zeros((len(posData), 3)) @@ -266,17 +301,25 @@ def run(showPlots=True): # Direction Cosine Matrix (DCM) from earth centered inertial frame to earth-moon rotation frame rSpacecraftMag = np.linalg.norm(posData[ii]) rMoonMag = np.linalg.norm(moon_rN) - DCM = [moon_rN / rMoonMag, moon_vN / np.linalg.norm(moon_vN), - np.cross(moon_rN, moon_vN) / np.linalg.norm(np.cross(moon_rN, moon_vN))] + DCM = [ + moon_rN / rMoonMag, + moon_vN / np.linalg.norm(moon_vN), + np.cross(moon_rN, moon_vN) / np.linalg.norm(np.cross(moon_rN, moon_vN)), + ] # Spacecraft position in rotating frame - rSpacecraft[ii,:] = np.dot(DCM, posData[ii]) - - plt.plot(rSpacecraft[:,0] / 1000, rSpacecraft[:,1] / 1000, - color='g', linewidth=3.0, label='Spacecraft') + rSpacecraft[ii, :] = np.dot(DCM, posData[ii]) + + plt.plot( + rSpacecraft[:, 0] / 1000, + rSpacecraft[:, 1] / 1000, + color="g", + linewidth=3.0, + label="Spacecraft", + ) - plt.xlabel('Earth-Moon axis [km]') - plt.ylabel('Moon Velocity axis [km]') + plt.xlabel("Earth-Moon axis [km]") + plt.ylabel("Moon Velocity axis [km]") plt.grid() plt.legend() pltName = fileName + "Fig2" @@ -288,16 +331,21 @@ def run(showPlots=True): fig = plt.figure(3, figsize=tuple(np.array((1.0, b / oe.a)) * 4.75), dpi=100) plt.axis(np.array([-1e5, 5e5, -3e5, 3e5]) * 1.25) ax = fig.gca() - ax.ticklabel_format(style='scientific', scilimits=[-5, 3]) + ax.ticklabel_format(style="scientific", scilimits=[-5, 3]) # Draw 'cartoon' Earth - ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b')) - - plt.plot(rSpacecraft[:, 0] / 1000, rSpacecraft[:, 2] / 1000, - color='g', linewidth=3.0, label='Spacecraft') + ax.add_artist(plt.Circle((0, 0), 0.2e5, color="b")) + + plt.plot( + rSpacecraft[:, 0] / 1000, + rSpacecraft[:, 2] / 1000, + color="g", + linewidth=3.0, + label="Spacecraft", + ) - plt.xlabel('Earth-Moon axis [km]') - plt.ylabel('Earth-Moon perpendicular axis [km]') + plt.xlabel("Earth-Moon axis [km]") + plt.ylabel("Earth-Moon perpendicular axis [km]") plt.grid() plt.legend() pltName = fileName + "Fig3" @@ -310,15 +358,14 @@ def run(showPlots=True): # Unload spice libraries gravFactory.unloadSpiceKernels() - pyswice.unload_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.unload_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.unload_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.unload_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel - + pyswice.unload_c(str(de430_path)) + pyswice.unload_c(str(naif0012_path)) + pyswice.unload_c(str(de403masses_path)) + pyswice.unload_c(str(pck00010_path)) return figureList if __name__ == "__main__": run( - True # Show plots + True # Show plots ) diff --git a/examples/scenarioHohmann.py b/examples/scenarioHohmann.py index 93055a9df6..25259f5b5d 100644 --- a/examples/scenarioHohmann.py +++ b/examples/scenarioHohmann.py @@ -231,8 +231,7 @@ def run(show_plots, rFirst, rSecond): # Override information with SPICE timeInitString = "2021 MAY 04 07:47:48.965 (UTC)" - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, + gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) gravFactory.spiceObject.zeroBase = 'Earth' gravFactory.addBodiesTo(scObject) diff --git a/examples/scenarioLagrangePointOrbit.py b/examples/scenarioLagrangePointOrbit.py index 3797f1ba7f..af27823e99 100644 --- a/examples/scenarioLagrangePointOrbit.py +++ b/examples/scenarioLagrangePointOrbit.py @@ -136,13 +136,21 @@ from Basilisk.simulation import orbElemConvert from Basilisk.simulation import spacecraft from Basilisk.topLevelModules import pyswice -from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion, - simIncludeGravBody, unitTestSupport, vizSupport) +from Basilisk.utilities import ( + SimulationBaseClass, + macros, + orbitalMotion, + simIncludeGravBody, + unitTestSupport, + vizSupport, +) from Basilisk.utilities.pyswice_spk_utilities import spkRead +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] fileName = os.path.basename(os.path.splitext(__file__)[0]) + def run(lagrangePoint, nOrbits, timestep, showPlots=True): """ Args: @@ -182,56 +190,60 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): # Setup gravity factory and gravity bodies # Include bodies as a list of SPICE names gravFactory = simIncludeGravBody.gravBodyFactory() - gravBodies = gravFactory.createBodies('moon', 'earth') - gravBodies['earth'].isCentralBody = True + gravBodies = gravFactory.createBodies("moon", "earth") + gravBodies["earth"].isCentralBody = True # Add gravity bodies to the spacecraft dynamics gravFactory.addBodiesTo(scObject) # Create default SPICE module, specify start date/time. timeInitString = "2022 August 31 15:00:00.0" - spiceTimeStringFormat = '%Y %B %d %H:%M:%S.%f' + spiceTimeStringFormat = "%Y %B %d %H:%M:%S.%f" timeInit = datetime.strptime(timeInitString, spiceTimeStringFormat) spiceObject = gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) - spiceObject.zeroBase = 'Earth' + spiceObject.zeroBase = "Earth" # Add SPICE object to the simulation task list scSim.AddModelToTask(simTaskName, spiceObject, 1) # Import SPICE ephemeris data into the python environment - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) # solar system bodies + pyswice.furnsh_c(str(naif0012_path)) # leap second file + pyswice.furnsh_c(str(de403masses_path)) # solar system masses + pyswice.furnsh_c(str(pck00010_path)) # generic Planetary Constants Kernel # Set spacecraft ICs # Use Earth data - moonSpiceName = 'moon' - moonInitialState = 1000 * spkRead(moonSpiceName, timeInitString, 'J2000', 'earth') + moonSpiceName = "moon" + moonInitialState = 1000 * spkRead(moonSpiceName, timeInitString, "J2000", "earth") moon_rN_init = moonInitialState[0:3] moon_vN_init = moonInitialState[3:6] - moon = gravBodies['moon'] - earth = gravBodies['earth'] + moon = gravBodies["moon"] + earth = gravBodies["earth"] oe = orbitalMotion.rv2elem(earth.mu, moon_rN_init, moon_vN_init) moon_a = oe.a # Delay or advance the spacecraft by a few degrees to prevent strange spacecraft-moon interactions when the # spacecraft wanders from the unstable equilibrium points if lagrangePoint == 1: - oe.a = oe.a * (1-np.power(moon.mu / (3*earth.mu), 1./3.)) - oe.f = oe.f + macros.D2R*4 + oe.a = oe.a * (1 - np.power(moon.mu / (3 * earth.mu), 1.0 / 3.0)) + oe.f = oe.f + macros.D2R * 4 elif lagrangePoint == 2: - oe.a = oe.a * (1+np.power(moon.mu / (3*earth.mu), 1./3.)) - oe.f = oe.f - macros.D2R*4 + oe.a = oe.a * (1 + np.power(moon.mu / (3 * earth.mu), 1.0 / 3.0)) + oe.f = oe.f - macros.D2R * 4 elif lagrangePoint == 3: - oe.a = oe.a * (1-(7*moon.mu/(12*earth.mu))) + oe.a = oe.a * (1 - (7 * moon.mu / (12 * earth.mu))) oe.f = oe.f + np.pi elif lagrangePoint == 4: - oe.f = oe.f + np.pi/3 + oe.f = oe.f + np.pi / 3 else: - oe.f = oe.f - np.pi/3 + oe.f = oe.f - np.pi / 3 - oe.f = oe.f - macros.D2R*2 + oe.f = oe.f - macros.D2R * 2 rN, vN = orbitalMotion.elem2rv(earth.mu, oe) @@ -240,20 +252,25 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): # Set simulation time n = np.sqrt(earth.mu / np.power(moon_a, 3)) - P = 2 * np.pi/n - simulationTime = macros.sec2nano(nOrbits*P) + P = 2 * np.pi / n + simulationTime = macros.sec2nano(nOrbits * P) # Setup data logging numDataPoints = 1000 - samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, simulationTimeStep, numDataPoints + ) # Setup spacecraft data recorder scDataRec = scObject.scStateOutMsg.recorder(samplingTime) scSim.AddModelToTask(simTaskName, scDataRec) - viz = vizSupport.enableUnityVisualization(scSim, simTaskName, scObject, - # saveFile=__file__ - ) + viz = vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + # saveFile=__file__ + ) # Initialize simulation scSim.InitializeSimulation() @@ -276,10 +293,10 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): fig = plt.figure(1, figsize=tuple(np.array((1.0, b / oe.a)) * 4.75), dpi=100) plt.axis(np.array([-oe.rApoap, oe.rPeriap, -b, b]) / 1000 * 1.25) ax = fig.gca() - ax.ticklabel_format(style='scientific', scilimits=[-5, 3]) + ax.ticklabel_format(style="scientific", scilimits=[-5, 3]) # Draw 'cartoon' Earth - ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b')) + ax.add_artist(plt.Circle((0, 0), 0.2e5, color="b")) # Plot spacecraft orbit data rDataSpacecraft = [] @@ -287,9 +304,16 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): for ii in range(len(posData)): oeDataSpacecraft = orbitalMotion.rv2elem(earth.mu, posData[ii], velData[ii]) rDataSpacecraft.append(oeDataSpacecraft.rmag) - fDataSpacecraft.append(oeDataSpacecraft.f + oeDataSpacecraft.omega - oe.omega) # Why the add/subtract of omegas? - plt.plot(rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, rDataSpacecraft * np.sin(fDataSpacecraft) / 1000, - color='g', linewidth=3.0, label='Spacecraft') + fDataSpacecraft.append( + oeDataSpacecraft.f + oeDataSpacecraft.omega - oe.omega + ) # Why the add/subtract of omegas? + plt.plot( + rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, + rDataSpacecraft * np.sin(fDataSpacecraft) / 1000, + color="g", + linewidth=3.0, + label="Spacecraft", + ) # Plot moon orbit data rDataMoon = [] @@ -300,17 +324,22 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): usec = (simTime - sec) * 1e6 time = timeInit + timedelta(seconds=sec, microseconds=usec) timeString = time.strftime(spiceTimeStringFormat) - moonState = 1000 * spkRead(moonSpiceName, timeString, 'J2000', 'earth') + moonState = 1000 * spkRead(moonSpiceName, timeString, "J2000", "earth") moon_rN = moonState[0:3] moon_vN = moonState[3:6] oeDataMoon = orbitalMotion.rv2elem(earth.mu, moon_rN, moon_vN) rDataMoon.append(oeDataMoon.rmag) fDataMoon.append(oeDataMoon.f + oeDataMoon.omega - oe.omega) - plt.plot(rDataMoon * np.cos(fDataMoon) / 1000, rDataMoon * np.sin(fDataMoon) / 1000, color='0.5', linewidth=3.0, - label='Moon') + plt.plot( + rDataMoon * np.cos(fDataMoon) / 1000, + rDataMoon * np.sin(fDataMoon) / 1000, + color="0.5", + linewidth=3.0, + label="Moon", + ) - plt.xlabel('$i_e$ Coord. [km]') - plt.ylabel('$i_p$ Coord. [km]') + plt.xlabel("$i_e$ Coord. [km]") + plt.ylabel("$i_p$ Coord. [km]") plt.grid() plt.legend() pltName = fileName + "L" + str(lagrangePoint) + "Fig1" @@ -320,10 +349,10 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): fig = plt.figure(2, figsize=tuple(np.array((1.0, b / oe.a)) * 4.75), dpi=100) plt.axis(np.array([-oe.rApoap, oe.rPeriap, -b, b]) / 1000 * 1.25) ax = fig.gca() - ax.ticklabel_format(style='scientific', scilimits=[-5, 3]) + ax.ticklabel_format(style="scientific", scilimits=[-5, 3]) # Draw 'cartoon' Earth - ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b')) + ax.add_artist(plt.Circle((0, 0), 0.2e5, color="b")) # Plot spacecraft and Moon orbit data rDataSpacecraft = [] @@ -331,14 +360,13 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): rDataMoon = [] fDataMoon = [] for ii in range(len(posData)): - # Get Moon f simTime = timeData[ii] * macros.NANO2SEC sec = int(simTime) usec = (simTime - sec) * 1e6 time = timeInit + timedelta(seconds=sec, microseconds=usec) timeString = time.strftime(spiceTimeStringFormat) - moonState = 1000 * spkRead(moonSpiceName, timeString, 'J2000', 'earth') + moonState = 1000 * spkRead(moonSpiceName, timeString, "J2000", "earth") moon_rN = moonState[0:3] moon_vN = moonState[3:6] oeDataMoon = orbitalMotion.rv2elem(earth.mu, moon_rN, moon_vN) @@ -347,19 +375,31 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): # Get spacecraft data, with spacecraft f = oe data f - moon f oeDataSpacecraft = orbitalMotion.rv2elem(earth.mu, posData[ii], velData[ii]) rDataSpacecraft.append(oeDataSpacecraft.rmag) - fDataSpacecraft.append(oeDataSpacecraft.f - moon_f + oeDataSpacecraft.omega - oe.omega) + fDataSpacecraft.append( + oeDataSpacecraft.f - moon_f + oeDataSpacecraft.omega - oe.omega + ) # Get Moon data rDataMoon.append(oeDataMoon.rmag) fDataMoon.append(0) - plt.plot(rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, rDataSpacecraft * np.sin(fDataSpacecraft) / 1000, - color='g', linewidth=3.0, label='Spacecraft') - plt.plot(rDataMoon * np.cos(fDataMoon) / 1000, rDataMoon * np.sin(fDataMoon) / 1000, color='0.5', linewidth=3.0, - label='Moon') + plt.plot( + rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, + rDataSpacecraft * np.sin(fDataSpacecraft) / 1000, + color="g", + linewidth=3.0, + label="Spacecraft", + ) + plt.plot( + rDataMoon * np.cos(fDataMoon) / 1000, + rDataMoon * np.sin(fDataMoon) / 1000, + color="0.5", + linewidth=3.0, + label="Moon", + ) - plt.xlabel('Earth-Moon axis [km]') - plt.ylabel('Earth-Moon perpendicular axis [km]') + plt.xlabel("Earth-Moon axis [km]") + plt.ylabel("Earth-Moon perpendicular axis [km]") plt.grid() plt.legend() pltName = fileName + "L" + str(lagrangePoint) + "Fig2" @@ -372,18 +412,17 @@ def run(lagrangePoint, nOrbits, timestep, showPlots=True): # Unload spice libraries gravFactory.unloadSpiceKernels() - pyswice.unload_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.unload_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.unload_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.unload_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel - + pyswice.unload_c(str(de430_path)) + pyswice.unload_c(str(naif0012_path)) + pyswice.unload_c(str(de403masses_path)) + pyswice.unload_c(str(pck00010_path)) return figureList if __name__ == "__main__": run( - 5, # Lagrange point - 1, # Number of Moon orbits - 300, # Timestep (seconds) - True # Show plots + 5, # Lagrange point + 1, # Number of Moon orbits + 300, # Timestep (seconds) + True, # Show plots ) diff --git a/examples/scenarioMonteCarloSpice.py b/examples/scenarioMonteCarloSpice.py index 4da783bb53..ce7df6b985 100644 --- a/examples/scenarioMonteCarloSpice.py +++ b/examples/scenarioMonteCarloSpice.py @@ -50,7 +50,6 @@ # Purpose: This Monte Carlo example shows how to properly use Spice in such simulations. # - import inspect import os import shutil @@ -62,6 +61,7 @@ # @endcond from Basilisk import __path__ + bskPath = __path__[0] from Basilisk.utilities import SimulationBaseClass @@ -72,6 +72,7 @@ from Basilisk.simulation import spacecraft from Basilisk.utilities.MonteCarlo.Controller import Controller +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile class MyController(Controller): @@ -81,12 +82,18 @@ def __init__(self): # Constructor for Monte Carlo simulations # Uncomment the following block to cause this MC scenario to fail # due to an incorrect usage of the pyswice module - # dataPath = bskPath + "/supportData/EphemerisData/" - # pyswice.furnsh_c(dataPath + 'naif0011.tls') - # pyswice.furnsh_c(dataPath + 'pck00010.tpc') - # pyswice.furnsh_c(dataPath + 'de-403-masses.tpc') - # pyswice.furnsh_c(dataPath + 'de430.bsp') - # pyswice.furnsh_c(dataPath + 'hst_edited.bsp') + # naif0011_path = get_path(DataFile.EphemerisData.naif0011) + # pck00010_path = get_path(DataFile.EphemerisData.pck00010) + # de403_path = get_path(DataFile.EphemerisData.de_403_masses) + # de430_path = get_path(DataFile.EphemerisData.de430) + # hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + + # self.scSpiceName = 'HUBBLE SPACE TELESCOPE' + # pyswice.furnsh_c(str(naif0011_path)) + # pyswice.furnsh_c(str(pck00010_path)) + # pyswice.furnsh_c(str(de403_path)) + # pyswice.furnsh_c(str(de430_path)) + # pyswice.furnsh_c(str(hst_edited_path)) class MySimulation(SimulationBaseClass.SimBaseClass): @@ -96,35 +103,39 @@ def __init__(self): simTaskName = "simTask" simProcessName = "simProcess" - self.dynProcess = self.CreateNewProcess(simProcessName) - self.dynProcess.addTask(self.CreateNewTask(simTaskName, macros.sec2nano(10.))) + self.dynProcess.addTask(self.CreateNewTask(simTaskName, macros.sec2nano(10.0))) self.scObject = spacecraft.Spacecraft() self.AddModelToTask(simTaskName, self.scObject, 1) - self.scObject.hub.r_CN_NInit = [7000000.0, 0.0, 0.0] # m - r_CN_N - self.scObject.hub.v_CN_NInit = [0.0, 7500.0, 0.0] # m/s - v_CN_N - + self.scObject.hub.r_CN_NInit = [7000000.0, 0.0, 0.0] # m - r_CN_N + self.scObject.hub.v_CN_NInit = [0.0, 7500.0, 0.0] # m/s - v_CN_N # operate on pyswice - dataPath = bskPath + "/supportData/EphemerisData/" - self.scSpiceName = 'HUBBLE SPACE TELESCOPE' - pyswice.furnsh_c(dataPath + 'naif0011.tls') - pyswice.furnsh_c(dataPath + 'pck00010.tpc') - pyswice.furnsh_c(dataPath + 'de-403-masses.tpc') - pyswice.furnsh_c(dataPath + 'de430.bsp') - pyswice.furnsh_c(dataPath + 'hst_edited.bsp') + naif0011_path = get_path(DataFile.EphemerisData.naif0011) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + de403_path = get_path(DataFile.EphemerisData.de_403_masses) + de430_path = get_path(DataFile.EphemerisData.de430) + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + + self.scSpiceName = "HUBBLE SPACE TELESCOPE" + pyswice.furnsh_c(str(naif0011_path)) + pyswice.furnsh_c(str(pck00010_path)) + pyswice.furnsh_c(str(de403_path)) + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(hst_edited_path)) self.accessSpiceKernel() def accessSpiceKernel(self): - startCalendarTime = '2012 APR 29 15:18:14.907 (UTC)' - zeroBase = 'Sun' - integFrame = 'j2000' + startCalendarTime = "2012 APR 29 15:18:14.907 (UTC)" + zeroBase = "Sun" + integFrame = "j2000" stateOut = spkRead(self.scSpiceName, startCalendarTime, integFrame, zeroBase) print(stateOut) + def run(): """ This is the main function that is called in this script. It illustrates possible ways @@ -145,11 +156,15 @@ def run(): # Here is another example where it is allowable to run the python spice routines within a MC simulation setup # - # dataPath = bskPath + "/supportData/EphemerisData/" - # pyswice.furnsh_c(dataPath + 'naif0011.tls') - # pyswice.furnsh_c(dataPath + 'pck00010.tpc') - # pyswice.furnsh_c(dataPath + 'de-403-masses.tpc') - # pyswice.furnsh_c(dataPath + 'de430.bsp') + # naif0011_path = get_path(DataFile.EphemerisData.naif0011) + # pck00010_path = get_path(DataFile.EphemerisData.pck00010) + # de403_path = get_path(DataFile.EphemerisData.de_403_masses) + # de430_path = get_path(DataFile.EphemerisData.de430) + + # pyswice.furnsh_c(str(naif0011_path)) + # pyswice.furnsh_c(str(pck00010_path)) + # pyswice.furnsh_c(str(de403_path)) + # pyswice.furnsh_c(str(de430_path)) # # startCalendarTime = '2012 AUG 05, 21:35:07.496 (UTC)' # startTimeArray = sim_model.new_doubleArray(1) @@ -166,16 +181,20 @@ def run(): def executeScenario(sim): - sim.ConfigureStopTime(macros.sec2nano(100.)) + sim.ConfigureStopTime(macros.sec2nano(100.0)) sim.InitializeSimulation() # Here is another example where it is allowable to run the python spice routines within a MC simulation setup # - # dataPath = bskPath + "/supportData/EphemerisData/" - # pyswice.furnsh_c(dataPath + 'naif0011.tls') - # pyswice.furnsh_c(dataPath + 'pck00010.tpc') - # pyswice.furnsh_c(dataPath + 'de-403-masses.tpc') - # pyswice.furnsh_c(dataPath + 'de430.bsp') + # naif0011_path = get_path(DataFile.EphemerisData.naif0011) + # pck00010_path = get_path(DataFile.EphemerisData.pck00010) + # de403_path = get_path(DataFile.EphemerisData.de_403_masses) + # de430_path = get_path(DataFile.EphemerisData.de430) + + # pyswice.furnsh_c(str(naif0011_path)) + # pyswice.furnsh_c(str(pck00010_path)) + # pyswice.furnsh_c(str(de403_path)) + # pyswice.furnsh_c(str(de430_path)) sim.ExecuteSimulation() diff --git a/examples/scenarioOrbitMultiBody.py b/examples/scenarioOrbitMultiBody.py index 9a7a8ed7b9..f4b6122c91 100755 --- a/examples/scenarioOrbitMultiBody.py +++ b/examples/scenarioOrbitMultiBody.py @@ -102,20 +102,27 @@ import matplotlib.pyplot as plt import numpy as np from Basilisk import __path__ + # import simulation related support from Basilisk.simulation import spacecraft + # Used to get the location of supporting data. from Basilisk.topLevelModules import pyswice + # import general simulation support files from Basilisk.utilities import SimulationBaseClass from Basilisk.utilities import macros from Basilisk.utilities import orbitalMotion from Basilisk.utilities import simIncludeGravBody -from Basilisk.utilities import unitTestSupport # general support file with common unit test functions +from Basilisk.utilities import ( + unitTestSupport, +) # general support file with common unit test functions from Basilisk.architecture import astroConstants + # attempt to import vizard from Basilisk.utilities import vizSupport from Basilisk.utilities.pyswice_spk_utilities import spkRead +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] @@ -152,7 +159,7 @@ def run(show_plots, scCase): dynProcess = scSim.CreateNewProcess(simProcessName) # create the dynamics task and specify the integration update time - simulationTimeStep = macros.sec2nano(5.) + simulationTimeStep = macros.sec2nano(5.0) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # @@ -181,12 +188,15 @@ def run(show_plots, scCase): # Next a series of gravitational bodies are included. Note that it is convenient to include them as a # list of SPICE names. The Earth is included in this scenario with the # spherical harmonics turned on. Note that this is true for both spacecraft simulations. - gravBodies = gravFactory.createBodies('earth', 'mars barycenter', 'sun', 'moon', "jupiter barycenter") - gravBodies['earth'].isCentralBody = True + gravBodies = gravFactory.createBodies( + "earth", "mars barycenter", "sun", "moon", "jupiter barycenter" + ) + gravBodies["earth"].isCentralBody = True # Other possible ways to access specific gravity bodies include the below # earth = gravBodies['earth'] # earth = gravFactory.createEarth() - gravBodies['earth'].useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S.txt', 100) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + gravBodies["earth"].useSphericalHarmonicsGravityModel(str(ggm03s_path), 100) # The configured gravitational bodies are added to the spacecraft dynamics with the usual command: gravFactory.addBodiesTo(scObject) @@ -194,7 +204,7 @@ def run(show_plots, scCase): # Next, the default SPICE support module is created and configured. The first step is to store # the date and time of the start of the simulation. timeInitString = "2012 MAY 1 00:28:30.0" - spiceTimeStringFormat = '%Y %B %d %H:%M:%S.%f' + spiceTimeStringFormat = "%Y %B %d %H:%M:%S.%f" timeInit = datetime.strptime(timeInitString, spiceTimeStringFormat) # The following is a support macro that creates a `spiceObject` instance, and fills in typical @@ -212,7 +222,7 @@ def run(show_plots, scCase): # or SSB for short. The spacecraft() state output message is relative to this SBB frame by default. To change # this behavior, the zero based point must be redefined from SBB to another body. # In this simulation we use the Earth. - spiceObject.zeroBase = 'Earth' + spiceObject.zeroBase = "Earth" # Finally, the SPICE object is added to the simulation task list. scSim.AddModelToTask(simTaskName, spiceObject) @@ -226,23 +236,28 @@ def run(show_plots, scCase): # separate from the earlier SPICE setup that was loaded to BSK. This is why # all required SPICE libraries must be included when setting up and loading # SPICE kernals in Python. - if scCase == 'NewHorizons': - scEphemerisFileName = 'nh_pred_od077.bsp' - scSpiceName = 'NEW HORIZONS' + if scCase == "NewHorizons": + scEphemerisPath = get_path(DataFile.EphemerisData.nh_pred_od077) + scSpiceName = "NEW HORIZONS" else: # default case - scEphemerisFileName = 'hst_edited.bsp' - scSpiceName = 'HUBBLE SPACE TELESCOPE' - pyswice.furnsh_c(spiceObject.SPICEDataPath + scEphemerisFileName) # Hubble Space Telescope data - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel + scSpiceName = "HUBBLE SPACE TELESCOPE" + scEphemerisPath = get_path(DataFile.EphemerisData.hst_edited) + + pyswice.furnsh_c(str(scEphemerisPath)) # Hubble Space Telescope data + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) # solar system bodies + pyswice.furnsh_c(str(naif0012_path)) # leap second file + pyswice.furnsh_c(str(de403masses_path)) # solar system masses + pyswice.furnsh_c(str(pck00010_path)) # generic Planetary Constants Kernel # # Setup spacecraft initial states # # The initial spacecraft position and velocity vector is obtained via the SPICE function call: - scInitialState = 1000 * spkRead(scSpiceName, timeInitString, 'J2000', 'EARTH') + scInitialState = 1000 * spkRead(scSpiceName, timeInitString, "J2000", "EARTH") rN = scInitialState[0:3] # meters vN = scInitialState[3:6] # m/s @@ -262,20 +277,25 @@ def run(show_plots, scCase): # # Setup simulation time # - simulationTime = macros.sec2nano(2000.) + simulationTime = macros.sec2nano(2000.0) # # Setup data logging before the simulation is initialized # numDataPoints = 50 - samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, simulationTimeStep, numDataPoints + ) dataRec = scObject.scStateOutMsg.recorder(samplingTime) scSim.AddModelToTask(simTaskName, dataRec) # if this scenario is to interface with the BSK Unity Viz, uncomment the following lines - vizSupport.enableUnityVisualization(scSim, simTaskName, scObject - # , saveFile=fileName - ) + vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + # , saveFile=fileName + ) # # initialize Simulation @@ -304,35 +324,38 @@ def run(show_plots, scCase): plt.figure(1) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') - ax.yaxis.set_major_formatter(matplotlib.ticker.StrMethodFormatter('{x:,.0f}')) - if scCase == 'NewHorizons': - axesScale = astroConstants.AU * 1000. # convert to AU - axesLabel = '[AU]' + ax.ticklabel_format(useOffset=False, style="plain") + ax.yaxis.set_major_formatter(matplotlib.ticker.StrMethodFormatter("{x:,.0f}")) + if scCase == "NewHorizons": + axesScale = astroConstants.AU * 1000.0 # convert to AU + axesLabel = "[AU]" timeScale = macros.NANO2MIN # convert to minutes - timeLabel = '[min]' + timeLabel = "[min]" else: - axesScale = 1000. # convert to km - axesLabel = '[km]' + axesScale = 1000.0 # convert to km + axesLabel = "[km]" timeScale = macros.NANO2MIN # convert to minutes - timeLabel = '[min]' + timeLabel = "[min]" for idx in range(3): - plt.plot(timeAxis * timeScale, posData[:, idx] / axesScale, - color=unitTestSupport.getLineColor(idx, 3), - label='$r_{BN,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time ' + timeLabel) - plt.ylabel('Inertial Position ' + axesLabel) + plt.plot( + timeAxis * timeScale, + posData[:, idx] / axesScale, + color=unitTestSupport.getLineColor(idx, 3), + label="$r_{BN," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time " + timeLabel) + plt.ylabel("Inertial Position " + axesLabel) figureList = {} pltName = fileName + "1" + scCase figureList[pltName] = plt.figure(1) rBSK = posData[-1] # store the last position to compare to the SPICE position - if scCase == 'Hubble': + if scCase == "Hubble": # # draw orbit in perifocal frame # - oeData = orbitalMotion.rv2elem(gravBodies['earth'].mu, rN, vN) + oeData = orbitalMotion.rv2elem(gravBodies["earth"].mu, rN, vN) omega0 = oeData.omega b = oeData.a * np.sqrt(1 - oeData.e * oeData.e) p = oeData.a * (1 - oeData.e * oeData.e) @@ -342,23 +365,27 @@ def run(show_plots, scCase): # draw the planet fig = plt.gcf() ax = fig.gca() - planetColor = '#008800' - planetRadius = gravBodies['earth'].radEquator / 1000 + planetColor = "#008800" + planetRadius = gravBodies["earth"].radEquator / 1000 ax.add_artist(plt.Circle((0, 0), planetRadius, color=planetColor)) # draw the actual orbit rData = [] fData = [] for idx in range(0, len(posData)): - oeData = orbitalMotion.rv2elem(gravBodies['earth'].mu, posData[idx], velData[idx]) + oeData = orbitalMotion.rv2elem( + gravBodies["earth"].mu, posData[idx], velData[idx] + ) rData.append(oeData.rmag) fData.append(oeData.f + oeData.omega - omega0) - plt.plot(rData * np.cos(fData) / 1000, rData * np.sin(fData) / 1000 - , color='#aa0000' - , linewidth=0.5 - , label='Basilisk' - ) - plt.legend(loc='lower right') + plt.plot( + rData * np.cos(fData) / 1000, + rData * np.sin(fData) / 1000, + color="#aa0000", + linewidth=0.5, + label="Basilisk", + ) + plt.legend(loc="lower right") # draw the full SPICE orbit rData = [] @@ -370,38 +397,39 @@ def run(show_plots, scCase): usec = (simTime - sec) * 1000000 time = timeInit + timedelta(seconds=sec, microseconds=usec) timeString = time.strftime(spiceTimeStringFormat) - scState = 1000.0 * spkRead(scSpiceName, timeString, 'J2000', 'EARTH') + scState = 1000.0 * spkRead(scSpiceName, timeString, "J2000", "EARTH") rN = scState[0:3] # meters vN = scState[3:6] # m/s - oeData = orbitalMotion.rv2elem(gravBodies['earth'].mu, rN, vN) + oeData = orbitalMotion.rv2elem(gravBodies["earth"].mu, rN, vN) rData.append(oeData.rmag) fData.append(oeData.f + oeData.omega - omega0) rTrue = rN # store the last position to compare to the BSK position - plt.plot(rData * np.cos(fData) / 1000, rData * np.sin(fData) / 1000 - , '--' - , color='#555555' - , linewidth=1.0 - , label='Spice' - ) - plt.legend(loc='lower right') - plt.xlabel('$i_e$ Cord. [km]') - plt.ylabel('$i_p$ Cord. [km]') + plt.plot( + rData * np.cos(fData) / 1000, + rData * np.sin(fData) / 1000, + "--", + color="#555555", + linewidth=1.0, + label="Spice", + ) + plt.legend(loc="lower right") + plt.xlabel("$i_e$ Cord. [km]") + plt.ylabel("$i_p$ Cord. [km]") plt.grid() pltName = fileName + "2" + scCase figureList[pltName] = plt.figure(2) else: - scState = 1000.0 * spkRead(scSpiceName, - spiceObject.getCurrentTimeString(), - 'J2000', - 'EARTH') + scState = 1000.0 * spkRead( + scSpiceName, spiceObject.getCurrentTimeString(), "J2000", "EARTH" + ) rTrue = scState[0:3] # plot the differences between BSK and SPICE position data plt.figure(3) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") # ax.yaxis.set_major_formatter(matplotlib.ticker.StrMethodFormatter('{x:,.0f}')) posError = [] for idx in range(len(timeAxis)): @@ -410,15 +438,18 @@ def run(show_plots, scCase): usec = (simTime - sec) * 1000000 time = timeInit + timedelta(seconds=sec, microseconds=usec) timeString = time.strftime(spiceTimeStringFormat) - scState = 1000 * spkRead(scSpiceName, timeString, 'J2000', 'EARTH') + scState = 1000 * spkRead(scSpiceName, timeString, "J2000", "EARTH") posError.append(posData[idx] - np.array(scState[0:3])) # meters for idx in range(3): - plt.plot(dataRec.times() * macros.NANO2MIN, np.array(posError)[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\Delta r_{' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [min]') - plt.ylabel('Inertial Position Differences [m]') + plt.plot( + dataRec.times() * macros.NANO2MIN, + np.array(posError)[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\Delta r_{" + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [min]") + plt.ylabel("Inertial Position Differences [m]") pltName = fileName + "3" + scCase figureList[pltName] = plt.figure(3) @@ -432,11 +463,11 @@ def run(show_plots, scCase): # unload the SPICE libraries that were loaded by the pyswice utility and the spiceObject earlier # gravFactory.unloadSpiceKernels() - pyswice.unload_c(scEphemerisFileName) - pyswice.unload_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies - pyswice.unload_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file - pyswice.unload_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses - pyswice.unload_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel + pyswice.unload_c(str(scEphemerisPath)) + pyswice.unload_c(str(de430_path)) # solar system bodies + pyswice.unload_c(str(naif0012_path)) # leap second file + pyswice.unload_c(str(de403masses_path)) # solar system masses + pyswice.unload_c(str(pck00010_path)) # generic Planetary Constants Kernel # each test method requires a single assert method to be called # this check below just makes sure no sub-test failures were found @@ -450,5 +481,5 @@ def run(show_plots, scCase): if __name__ == "__main__": run( True, # show_plots - 'Hubble' # 'Hubble' or 'NewHorizons' + "Hubble", # 'Hubble' or 'NewHorizons' ) diff --git a/examples/scenarioRoboticArm.py b/examples/scenarioRoboticArm.py index 8bbef3d4b3..5b7ce9b8b2 100644 --- a/examples/scenarioRoboticArm.py +++ b/examples/scenarioRoboticArm.py @@ -77,8 +77,19 @@ import matplotlib.pyplot as plt import numpy as np -from Basilisk.utilities import (SimulationBaseClass, vizSupport, simIncludeGravBody, macros, orbitalMotion, unitTestSupport) -from Basilisk.simulation import spacecraft, spinningBodyNDOFStateEffector, prescribedRotation1DOF +from Basilisk.utilities import ( + SimulationBaseClass, + vizSupport, + simIncludeGravBody, + macros, + orbitalMotion, + unitTestSupport, +) +from Basilisk.simulation import ( + spacecraft, + spinningBodyNDOFStateEffector, + prescribedRotation1DOF, +) from Basilisk.architecture import messaging from Basilisk import __path__ @@ -119,7 +130,7 @@ class geometryClass: def createSimBaseClass(): scSim = SimulationBaseClass.SimBaseClass() - scSim.simulationTime = macros.min2nano(5.) + scSim.simulationTime = macros.min2nano(5.0) scSim.dynTaskName = "dynTask" scSim.dynProcess = scSim.CreateNewProcess("dynProcess") dynTimeStep = macros.sec2nano(0.2) @@ -132,9 +143,29 @@ def setUpSpacecraft(scSim, scGeometry): scObject = spacecraft.Spacecraft() scObject.ModelTag = "scObject" scObject.hub.mHub = scGeometry.massHub - scObject.hub.IHubPntBc_B = [[scGeometry.massHub / 12 * (scGeometry.lengthHub ** 2 + scGeometry.heightHub ** 2), 0.0, 0.0], - [0.0, scGeometry.massHub / 12 * (scGeometry.widthHub ** 2 + scGeometry.heightHub ** 2), 0.0], - [0.0, 0.0, scGeometry.massHub / 12 * (scGeometry.lengthHub ** 2 + scGeometry.widthHub ** 2)]] + scObject.hub.IHubPntBc_B = [ + [ + scGeometry.massHub + / 12 + * (scGeometry.lengthHub**2 + scGeometry.heightHub**2), + 0.0, + 0.0, + ], + [ + 0.0, + scGeometry.massHub + / 12 + * (scGeometry.widthHub**2 + scGeometry.heightHub**2), + 0.0, + ], + [ + 0.0, + 0.0, + scGeometry.massHub + / 12 + * (scGeometry.lengthHub**2 + scGeometry.widthHub**2), + ], + ] scSim.AddModelToTask(scSim.dynTaskName, scObject) return scObject @@ -158,7 +189,13 @@ def createFirstLink(scSim, spinningBodyEffector, scGeometry): spinningBody.setISPntSc_S([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) spinningBody.setR_ScS_S([[0.0], [scGeometry.heightLink / 2], [0.0]]) - spinningBody.setR_SP_P([[0], [scGeometry.lengthHub / 2], [scGeometry.heightHub / 2 - scGeometry.diameterLink / 2]]) + spinningBody.setR_SP_P( + [ + [0], + [scGeometry.lengthHub / 2], + [scGeometry.heightHub / 2 - scGeometry.diameterLink / 2], + ] + ) spinningBody.setSHat_S([[1], [0], [0]]) spinningBody.setThetaInit(0 * macros.D2R) spinningBody.setThetaDotInit(0 * macros.D2R) @@ -172,20 +209,44 @@ def createFirstLink(scSim, spinningBodyEffector, scGeometry): profiler.setThetaInit(spinningBody.getThetaInit()) profiler.setSmoothingDuration(10) scSim.AddModelToTask(scSim.dynTaskName, profiler) - spinningBodyEffector.spinningBodyRefInMsgs[0].subscribeTo(profiler.spinningBodyOutMsg) + spinningBodyEffector.spinningBodyRefInMsgs[0].subscribeTo( + profiler.spinningBodyOutMsg + ) hingedRigidBodyMessageData = messaging.HingedRigidBodyMsgPayload( theta=-30 * macros.D2R, # [rad] thetaDot=0.0, # [rad/s] ) - hingedRigidBodyMessage1 = messaging.HingedRigidBodyMsg().write(hingedRigidBodyMessageData) + hingedRigidBodyMessage1 = messaging.HingedRigidBodyMsg().write( + hingedRigidBodyMessageData + ) profiler.spinningBodyInMsg.subscribeTo(hingedRigidBodyMessage1) spinningBody = spinningBodyNDOFStateEffector.SpinningBody() spinningBody.setMass(scGeometry.massLink) - spinningBody.setISPntSc_S([[spinningBody.getMass() / 12 * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink ** 2), 0.0, 0.0], - [0.0, spinningBody.getMass() / 12 * (scGeometry.diameterLink / 2) ** 2, 0.0], - [0.0, 0.0, spinningBody.getMass() / 12 * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink ** 2)]]) + spinningBody.setISPntSc_S( + [ + [ + spinningBody.getMass() + / 12 + * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink**2), + 0.0, + 0.0, + ], + [ + 0.0, + spinningBody.getMass() / 12 * (scGeometry.diameterLink / 2) ** 2, + 0.0, + ], + [ + 0.0, + 0.0, + spinningBody.getMass() + / 12 + * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink**2), + ], + ] + ) spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) spinningBody.setR_ScS_S([[0.0], [scGeometry.heightLink / 2], [0.0]]) spinningBody.setR_SP_P([[0], [0], [0]]) @@ -202,13 +263,17 @@ def createFirstLink(scSim, spinningBodyEffector, scGeometry): profiler.setThetaInit(spinningBody.getThetaInit()) profiler.setSmoothingDuration(10) scSim.AddModelToTask(scSim.dynTaskName, profiler) - spinningBodyEffector.spinningBodyRefInMsgs[1].subscribeTo(profiler.spinningBodyOutMsg) + spinningBodyEffector.spinningBodyRefInMsgs[1].subscribeTo( + profiler.spinningBodyOutMsg + ) hingedRigidBodyMessageData = messaging.HingedRigidBodyMsgPayload( theta=45 * macros.D2R, # [rad] thetaDot=0.0, # [rad/s] ) - hingedRigidBodyMessage2 = messaging.HingedRigidBodyMsg().write(hingedRigidBodyMessageData) + hingedRigidBodyMessage2 = messaging.HingedRigidBodyMsg().write( + hingedRigidBodyMessageData + ) profiler.spinningBodyInMsg.subscribeTo(hingedRigidBodyMessage2) @@ -232,20 +297,44 @@ def createSecondLink(scSim, spinningBodyEffector, scGeometry): profiler.setThetaInit(spinningBody.getThetaInit()) profiler.setSmoothingDuration(10) scSim.AddModelToTask(scSim.dynTaskName, profiler) - spinningBodyEffector.spinningBodyRefInMsgs[2].subscribeTo(profiler.spinningBodyOutMsg) + spinningBodyEffector.spinningBodyRefInMsgs[2].subscribeTo( + profiler.spinningBodyOutMsg + ) hingedRigidBodyMessageData = messaging.HingedRigidBodyMsgPayload( theta=90 * macros.D2R, # [rad] thetaDot=0.0, # [rad/s] ) - hingedRigidBodyMessage1 = messaging.HingedRigidBodyMsg().write(hingedRigidBodyMessageData) + hingedRigidBodyMessage1 = messaging.HingedRigidBodyMsg().write( + hingedRigidBodyMessageData + ) profiler.spinningBodyInMsg.subscribeTo(hingedRigidBodyMessage1) spinningBody = spinningBodyNDOFStateEffector.SpinningBody() spinningBody.setMass(scGeometry.massLink) - spinningBody.setISPntSc_S([[spinningBody.getMass() / 12 * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink ** 2), 0.0, 0.0], - [0.0, spinningBody.getMass() / 12 * (scGeometry.diameterLink / 2) ** 2, 0.0], - [0.0, 0.0, spinningBody.getMass() / 12 * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink ** 2)]]) + spinningBody.setISPntSc_S( + [ + [ + spinningBody.getMass() + / 12 + * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink**2), + 0.0, + 0.0, + ], + [ + 0.0, + spinningBody.getMass() / 12 * (scGeometry.diameterLink / 2) ** 2, + 0.0, + ], + [ + 0.0, + 0.0, + spinningBody.getMass() + / 12 + * (3 * (scGeometry.diameterLink / 2) ** 2 + scGeometry.heightLink**2), + ], + ] + ) spinningBody.setDCM_S0P([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) spinningBody.setR_ScS_S([[0.0], [scGeometry.heightLink / 2], [0.0]]) spinningBody.setR_SP_P([[0], [0], [0]]) @@ -262,26 +351,30 @@ def createSecondLink(scSim, spinningBodyEffector, scGeometry): profiler.setThetaInit(spinningBody.getThetaInit()) profiler.setSmoothingDuration(10) scSim.AddModelToTask(scSim.dynTaskName, profiler) - spinningBodyEffector.spinningBodyRefInMsgs[3].subscribeTo(profiler.spinningBodyOutMsg) + spinningBodyEffector.spinningBodyRefInMsgs[3].subscribeTo( + profiler.spinningBodyOutMsg + ) hingedRigidBodyMessageData = messaging.HingedRigidBodyMsgPayload( - theta=-20 * macros.D2R, # [rad] - thetaDot=0.0, # [rad/s] + theta=-20 * macros.D2R, # [rad] + thetaDot=0.0, # [rad/s] + ) + hingedRigidBodyMessage2 = messaging.HingedRigidBodyMsg().write( + hingedRigidBodyMessageData ) - hingedRigidBodyMessage2 = messaging.HingedRigidBodyMsg().write(hingedRigidBodyMessageData) profiler.spinningBodyInMsg.subscribeTo(hingedRigidBodyMessage2) def setUpGravity(scSim, scObject): gravFactory = simIncludeGravBody.gravBodyFactory() - gravBodies = gravFactory.createBodies(['earth', 'sun']) - gravBodies['earth'].isCentralBody = True - mu = gravBodies['earth'].mu + gravBodies = gravFactory.createBodies(["earth", "sun"]) + gravBodies["earth"].isCentralBody = True + mu = gravBodies["earth"].mu gravFactory.addBodiesTo(scObject) timeInitString = "2012 MAY 1 00:28:30.0" - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', timeInitString, epochInMsg=True) - gravFactory.spiceObject.zeroBase = 'earth' + gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) + gravFactory.spiceObject.zeroBase = "earth" scSim.AddModelToTask(scSim.dynTaskName, gravFactory.spiceObject) return mu @@ -314,29 +407,48 @@ def setUpRecorders(scSim, scObject, roboticArmEffector): def setUpVizard(scSim, scObject, roboticArmEffector, scGeometry): - scBodyList = [scObject, - ["arm1", roboticArmEffector.spinningBodyConfigLogOutMsgs[1]], - ["arm2", roboticArmEffector.spinningBodyConfigLogOutMsgs[3]]] - - viz = vizSupport.enableUnityVisualization(scSim, scSim.dynTaskName, scBodyList - # , saveFile=fileName - ) - - vizSupport.createCustomModel(viz - , simBodiesToModify=[scObject.ModelTag] - , modelPath="CUBE" - , color=vizSupport.toRGBA255("gold") - , scale=[scGeometry.widthHub, scGeometry.lengthHub, scGeometry.heightHub]) - vizSupport.createCustomModel(viz - , simBodiesToModify=["arm1"] - , modelPath="CYLINDER" - , scale=[scGeometry.diameterLink, scGeometry.diameterLink, scGeometry.heightLink / 2] - , rotation=[np.pi / 2, 0, 0]) - vizSupport.createCustomModel(viz - , simBodiesToModify=["arm2"] - , modelPath="CYLINDER" - , scale=[scGeometry.diameterLink, scGeometry.diameterLink, scGeometry.heightLink / 2] - , rotation=[np.pi / 2, 0, 0]) + scBodyList = [ + scObject, + ["arm1", roboticArmEffector.spinningBodyConfigLogOutMsgs[1]], + ["arm2", roboticArmEffector.spinningBodyConfigLogOutMsgs[3]], + ] + + viz = vizSupport.enableUnityVisualization( + scSim, + scSim.dynTaskName, + scBodyList, + # , saveFile=fileName + ) + + vizSupport.createCustomModel( + viz, + simBodiesToModify=[scObject.ModelTag], + modelPath="CUBE", + color=vizSupport.toRGBA255("gold"), + scale=[scGeometry.widthHub, scGeometry.lengthHub, scGeometry.heightHub], + ) + vizSupport.createCustomModel( + viz, + simBodiesToModify=["arm1"], + modelPath="CYLINDER", + scale=[ + scGeometry.diameterLink, + scGeometry.diameterLink, + scGeometry.heightLink / 2, + ], + rotation=[np.pi / 2, 0, 0], + ) + vizSupport.createCustomModel( + viz, + simBodiesToModify=["arm2"], + modelPath="CYLINDER", + scale=[ + scGeometry.diameterLink, + scGeometry.diameterLink, + scGeometry.heightLink / 2, + ], + rotation=[np.pi / 2, 0, 0], + ) viz.settings.orbitLinesOn = -1 @@ -363,11 +475,11 @@ def plotting(show_plots, scLog, thetaLog): plt.clf() ax = plt.axes() for idx, angle in enumerate(theta): - plt.plot(dynTimeMin, macros.R2D * angle, label=r'$\theta_' + str(idx + 1) + '$') - plt.legend(fontsize='14') - plt.title('Angles', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\theta$ [deg]', fontsize='18') + plt.plot(dynTimeMin, macros.R2D * angle, label=r"$\theta_" + str(idx + 1) + "$") + plt.legend(fontsize="14") + plt.title("Angles", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\theta$ [deg]", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -378,11 +490,15 @@ def plotting(show_plots, scLog, thetaLog): plt.clf() ax = plt.axes() for idx, angleRate in enumerate(thetaDot): - plt.plot(dynTimeMin, macros.R2D * angleRate, label=r'$\dot{\theta}_' + str(idx + 1) + '$') - plt.legend(fontsize='14') - plt.title('Angle Rates', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\dot{\theta}$ [deg/s]', fontsize='18') + plt.plot( + dynTimeMin, + macros.R2D * angleRate, + label=r"$\dot{\theta}_" + str(idx + 1) + "$", + ) + plt.legend(fontsize="14") + plt.title("Angle Rates", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\dot{\theta}$ [deg/s]", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -393,13 +509,16 @@ def plotting(show_plots, scLog, thetaLog): plt.clf() ax = plt.axes() for idx in range(3): - plt.plot(dynTimeMin, sigma_BN[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\sigma_' + str(idx) + '$') - plt.legend(fontsize='14') - plt.title('Attitude', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\sigma_{B/N}$', fontsize='18') + plt.plot( + dynTimeMin, + sigma_BN[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\sigma_" + str(idx) + "$", + ) + plt.legend(fontsize="14") + plt.title("Attitude", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\sigma_{B/N}$", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) @@ -410,13 +529,16 @@ def plotting(show_plots, scLog, thetaLog): plt.clf() ax = plt.axes() for idx in range(3): - plt.plot(dynTimeMin, omega_BN[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\omega_' + str(idx) + '$') - plt.legend(fontsize='14') - plt.title('Attitude Rate', fontsize='22') - plt.xlabel('time [min]', fontsize='18') - plt.ylabel(r'$\omega_{B/N}$', fontsize='18') + plt.plot( + dynTimeMin, + omega_BN[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\omega_" + str(idx) + "$", + ) + plt.legend(fontsize="14") + plt.title("Attitude Rate", fontsize="22") + plt.xlabel("time [min]", fontsize="18") + plt.ylabel(r"$\omega_{B/N}$", fontsize="18") plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax.yaxis.offsetText.set_fontsize(14) diff --git a/examples/scenarioSmallBodyLandmarks.py b/examples/scenarioSmallBodyLandmarks.py index 73bbce9e70..dece86c392 100644 --- a/examples/scenarioSmallBodyLandmarks.py +++ b/examples/scenarioSmallBodyLandmarks.py @@ -141,7 +141,10 @@ from Basilisk.utilities import orbitalMotion from Basilisk.utilities import simIncludeGravBody from Basilisk.utilities import RigidBodyKinematics -from Basilisk.utilities import unitTestSupport # general support file with common unit test functions +from Basilisk.utilities import ( + unitTestSupport, +) # general support file with common unit test functions +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile # attempt to import vizard from Basilisk.utilities import vizSupport @@ -152,26 +155,26 @@ # Landmark distribution function def landmark_distribution(vert_list, face_list, n_vert, n_face, n_lmk): - """Creates a landmark distribution based on a polyhedron shape.""" - pos_vert = np.array(vert_list) - order_face = np.array(face_list) - 1 - pos_face = np.zeros((n_face,3)) - normal_face = np.zeros((n_face,3)) - for i in range(n_face): - pos0 = pos_vert[order_face[i, 0], 0:3] - pos1 = pos_vert[order_face[i, 1], 0:3] - pos2 = pos_vert[order_face[i, 2], 0:3] - pos_face[i, 0:3] = (pos0 + pos1 + pos2) / 3 - normal_face[i, 0:3] = np.cross(pos1-pos0, pos2-pos0) - normal_face[i, 0:3] /= np.linalg.norm(normal_face[i, 0:3]) - - np.random.seed(0) - idx_lmk = np.random.choice(n_face, n_lmk, replace=False) - idx_lmk.sort() - pos_lmk = pos_face[idx_lmk, 0:3] - normal_lmk = normal_face[idx_lmk, 0:3] - - return pos_lmk, normal_lmk + """Creates a landmark distribution based on a polyhedron shape.""" + pos_vert = np.array(vert_list) + order_face = np.array(face_list) - 1 + pos_face = np.zeros((n_face, 3)) + normal_face = np.zeros((n_face, 3)) + for i in range(n_face): + pos0 = pos_vert[order_face[i, 0], 0:3] + pos1 = pos_vert[order_face[i, 1], 0:3] + pos2 = pos_vert[order_face[i, 2], 0:3] + pos_face[i, 0:3] = (pos0 + pos1 + pos2) / 3 + normal_face[i, 0:3] = np.cross(pos1 - pos0, pos2 - pos0) + normal_face[i, 0:3] /= np.linalg.norm(normal_face[i, 0:3]) + + np.random.seed(0) + idx_lmk = np.random.choice(n_face, n_lmk, replace=False) + idx_lmk.sort() + pos_lmk = pos_face[idx_lmk, 0:3] + normal_lmk = normal_face[idx_lmk, 0:3] + + return pos_lmk, normal_lmk def plot_3D(t, r, xyz_vert, order_face, posLmk, isvisibleLmk, dcm_CP): @@ -181,25 +184,76 @@ def plot_3D(t, r, xyz_vert, order_face, posLmk, isvisibleLmk, dcm_CP): color_asteroid = [105 / 255, 105 / 255, 105 / 255] fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.plot(r[0] / 1000, r[1] / 1000, r[2] / 1000, 'k', marker='s', markersize=5) - ax.plot_trisurf(xyz_vert[:, 0] / 1000, xyz_vert[:, 1] / 1000, xyz_vert[:, 2] / 1000, - triangles=order_face-1, color=color_asteroid, zorder=0, alpha=0.1) - ax.plot(posLmk[idxOn, 0] / 1000, posLmk[idxOn, 1] / 1000, posLmk[idxOn, 2] / 1000, 'b', linestyle='', - marker='.', markersize=5) - ax.plot(posLmk[idxOff, 0] / 1000, posLmk[idxOff, 1] / 1000, posLmk[idxOff, 2] / 1000, 'r', linestyle='', - marker='.', markersize=5, alpha=0.25) - ax.quiver(r[0] / 1000, r[1] / 1000, r[2] / 1000, dcm_CP[0,0], dcm_CP[1,0], dcm_CP[2,0], length=10, normalize=True, - color='black', alpha=0.25) - ax.quiver(r[0] / 1000, r[1] / 1000, r[2] / 1000, dcm_CP[0,1], dcm_CP[1,1], dcm_CP[2,1], length=10, normalize=True, - color='black', alpha=0.25) - ax.quiver(r[0] / 1000, r[1] / 1000, r[2] / 1000, dcm_CP[0,2], dcm_CP[1,2], dcm_CP[2,2], length=10, normalize=True, - color='blue') - ax.set_xlabel('${}^{P}r_{x}$ [km]') - ax.set_ylabel('${}^{P}r_{y}$ [km]') - ax.set_zlabel('${}^{P}r_{z}$ [km]') - t_str = str(int(t/60)) - ax.set_title('Asteroid rotating frame, t=' + t_str + ' min') + ax = fig.add_subplot(1, 1, 1, projection="3d") + ax.plot(r[0] / 1000, r[1] / 1000, r[2] / 1000, "k", marker="s", markersize=5) + ax.plot_trisurf( + xyz_vert[:, 0] / 1000, + xyz_vert[:, 1] / 1000, + xyz_vert[:, 2] / 1000, + triangles=order_face - 1, + color=color_asteroid, + zorder=0, + alpha=0.1, + ) + ax.plot( + posLmk[idxOn, 0] / 1000, + posLmk[idxOn, 1] / 1000, + posLmk[idxOn, 2] / 1000, + "b", + linestyle="", + marker=".", + markersize=5, + ) + ax.plot( + posLmk[idxOff, 0] / 1000, + posLmk[idxOff, 1] / 1000, + posLmk[idxOff, 2] / 1000, + "r", + linestyle="", + marker=".", + markersize=5, + alpha=0.25, + ) + ax.quiver( + r[0] / 1000, + r[1] / 1000, + r[2] / 1000, + dcm_CP[0, 0], + dcm_CP[1, 0], + dcm_CP[2, 0], + length=10, + normalize=True, + color="black", + alpha=0.25, + ) + ax.quiver( + r[0] / 1000, + r[1] / 1000, + r[2] / 1000, + dcm_CP[0, 1], + dcm_CP[1, 1], + dcm_CP[2, 1], + length=10, + normalize=True, + color="black", + alpha=0.25, + ) + ax.quiver( + r[0] / 1000, + r[1] / 1000, + r[2] / 1000, + dcm_CP[0, 2], + dcm_CP[1, 2], + dcm_CP[2, 2], + length=10, + normalize=True, + color="blue", + ) + ax.set_xlabel("${}^{P}r_{x}$ [km]") + ax.set_ylabel("${}^{P}r_{y}$ [km]") + ax.set_zlabel("${}^{P}r_{z}$ [km]") + t_str = str(int(t / 60)) + ax.set_title("Asteroid rotating frame, t=" + t_str + " min") def plot_pixel(pixelLmk, statusLmk): @@ -209,45 +263,54 @@ def plot_pixel(pixelLmk, statusLmk): fig = plt.figure() ax = fig.gca() - plt.plot(pixelOn[:,0], pixelOn[:,1], linestyle='', marker='s', color='b', markersize=5) + plt.plot( + pixelOn[:, 0], pixelOn[:, 1], linestyle="", marker="s", color="b", markersize=5 + ) for i in range(len(pixelOn)): - ax.annotate(str(i), (pixelOn[i,0], pixelOn[i,1])) + ax.annotate(str(i), (pixelOn[i, 0], pixelOn[i, 1])) ax.set_xlim([-1024, 1024]) ax.set_ylim([-768, 768]) - plt.xlabel('$p_x$ [-]') - plt.ylabel('$p_y$ [-]') + plt.xlabel("$p_x$ [-]") + plt.ylabel("$p_y$ [-]") def plot_nLmk(t, nvisibleLmk): """Plot visible landmarks evolution.""" fig = plt.figure() ax = fig.gca() - plt.plot(t/3600, nvisibleLmk, linestyle='--', marker='.', markersize=8) - ax.set_xlim([t[0]/3600, t[-1]/3600]) - plt.xlabel('Time [h]') - plt.ylabel('Visible landmarks [-]') + plt.plot(t / 3600, nvisibleLmk, linestyle="--", marker=".", markersize=8) + ax.set_xlim([t[0] / 3600, t[-1] / 3600]) + plt.xlabel("Time [h]") + plt.ylabel("Visible landmarks [-]") def plot_orientation(t, dcm_HP, dcm_CP): """Plot the camera frame orientation with respect to Hill frame.""" data = np.zeros((len(t), 3)) for i in range(len(t)): - data[i,0:3] = [np.dot(dcm_HP[i, 0:3, 0], dcm_CP[i, 0:3, 2]), - np.dot(dcm_HP[i, 0:3, 1], dcm_CP[i, 0:3, 0]), - np.dot(dcm_HP[i, 0:3, 2], dcm_CP[i, 0:3, 1])] + data[i, 0:3] = [ + np.dot(dcm_HP[i, 0:3, 0], dcm_CP[i, 0:3, 2]), + np.dot(dcm_HP[i, 0:3, 1], dcm_CP[i, 0:3, 0]), + np.dot(dcm_HP[i, 0:3, 2], dcm_CP[i, 0:3, 1]), + ] fig = plt.figure() ax = fig.gca() - labelStrings = (r'$\hat\imath_r\cdot \hat c_3$' - , r'${\hat\imath}_{\theta}\cdot \hat c_1$' - , r'$\hat\imath_h\cdot \hat c_2$') + labelStrings = ( + r"$\hat\imath_r\cdot \hat c_3$", + r"${\hat\imath}_{\theta}\cdot \hat c_1$", + r"$\hat\imath_h\cdot \hat c_2$", + ) for idx in range(3): - plt.plot(t/3600, data[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=labelStrings[idx]) - ax.set_xlim([t[0]/3600, t[-1]/3600]) - plt.legend(loc='lower right') - plt.xlabel('Time [h]') - plt.ylabel('Orientation Illustration') + plt.plot( + t / 3600, + data[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=labelStrings[idx], + ) + ax.set_xlim([t[0] / 3600, t[-1] / 3600]) + plt.legend(loc="lower right") + plt.xlabel("Time [h]") + plt.ylabel("Orientation Illustration") def run(show_plots, useBatch): @@ -268,7 +331,7 @@ def run(show_plots, useBatch): simProcessName = "simProcess" # Define the simulation duration - simulationTime = macros.min2nano(100.) + simulationTime = macros.min2nano(100.0) # Create the process dynProcess = scSim.CreateNewProcess(simProcessName) @@ -283,33 +346,35 @@ def run(show_plots, useBatch): # Setup celestial object ephemeris module gravBodyEphem = planetEphemeris.PlanetEphemeris() - gravBodyEphem.ModelTag = 'erosEphemeris' + gravBodyEphem.ModelTag = "erosEphemeris" gravBodyEphem.setPlanetNames(planetEphemeris.StringVector(["eros"])) # Specify asteroid orbit elements and rotational state January 21st, 2022 # https://ssd.jpl.nasa.gov/horizons.cgi#results oeAsteroid = planetEphemeris.ClassicElements() - oeAsteroid.a = 1.4583 * 149597870.7*1e3 # meters + oeAsteroid.a = 1.4583 * 149597870.7 * 1e3 # meters oeAsteroid.e = 0.2227 - oeAsteroid.i = 10.829 * np.pi/180 - oeAsteroid.Omega = 304.3 * np.pi/180 - oeAsteroid.omega = 178.9 * np.pi/180 - oeAsteroid.f = 246.9 * np.pi/180 - AR = 11.369 * np.pi/180 - dec = 17.227 * np.pi/180 + oeAsteroid.i = 10.829 * np.pi / 180 + oeAsteroid.Omega = 304.3 * np.pi / 180 + oeAsteroid.omega = 178.9 * np.pi / 180 + oeAsteroid.f = 246.9 * np.pi / 180 + AR = 11.369 * np.pi / 180 + dec = 17.227 * np.pi / 180 lst0 = 0 * macros.D2R gravBodyEphem.planetElements = planetEphemeris.classicElementVector([oeAsteroid]) gravBodyEphem.rightAscension = planetEphemeris.DoubleVector([AR]) gravBodyEphem.declination = planetEphemeris.DoubleVector([dec]) gravBodyEphem.lst0 = planetEphemeris.DoubleVector([lst0]) - gravBodyEphem.rotRate = planetEphemeris.DoubleVector([360 * macros.D2R / (5.27 * 3600.)]) - dcm_PN = RigidBodyKinematics.euler3232C([AR, np.pi/2 - dec, lst0]) + gravBodyEphem.rotRate = planetEphemeris.DoubleVector( + [360 * macros.D2R / (5.27 * 3600.0)] + ) + dcm_PN = RigidBodyKinematics.euler3232C([AR, np.pi / 2 - dec, lst0]) # Set up asteroid gravity effector (only Keplerian gravity for simplicity) # https://ssd.jpl.nasa.gov/tools/gravity.html#/vesta gravFactory = simIncludeGravBody.gravBodyFactory() mu = 4.4631 * 1e5 - asteroid = gravFactory.createCustomGravObject("eros", mu=mu, radEquator=16*1000) + asteroid = gravFactory.createCustomGravObject("eros", mu=mu, radEquator=16 * 1000) asteroid.isCentralBody = True asteroid.planetBodyInMsg.subscribeTo(gravBodyEphem.planetOutMsgs[0]) @@ -321,9 +386,7 @@ def run(show_plots, useBatch): # Initialize spacecraft object and set properties scObject = spacecraft.Spacecraft() scObject.ModelTag = "bsk-Sat" - I = [900., 0., 0., - 0., 800., 0., - 0., 0., 600.] + I = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0] scObject.hub.mHub = 750.0 scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) @@ -379,26 +442,26 @@ def run(show_plots, useBatch): mrpControl.K = 3.5 mrpControl.Ki = -1.0 # make value negative to turn off integral feedback mrpControl.P = 30.0 - mrpControl.integralLimit = 2. / mrpControl.Ki * 0.1 + mrpControl.integralLimit = 2.0 / mrpControl.Ki * 0.1 # Connect torque command to external torque effector extFTObject.cmdTorqueInMsg.subscribeTo(mrpControl.cmdTorqueOutMsg) # Prepare 100 landmarks distribution - polyFile = bskPath + '/supportData/LocalGravData/eros007790.tab' - vert_list, face_list, n_vert, n_face = loadPolyFromFileToList(polyFile) + poly_path = get_path(DataFile.LocalGravData.eros007790) + vert_list, face_list, n_vert, n_face = loadPolyFromFileToList(str(poly_path)) n_lmk = 100 - pos_lmk, normal_lmk = landmark_distribution(vert_list, face_list, n_vert, n_face, n_lmk) + pos_lmk, normal_lmk = landmark_distribution( + vert_list, face_list, n_vert, n_face, n_lmk + ) # Set the pinhole camera module camera = pinholeCamera.PinholeCamera() - camera.f = 25*1e-3 + camera.f = 25 * 1e-3 camera.nxPixel = 2048 camera.nyPixel = 1536 - camera.wPixel = (17.3*1e-3) / 2048 - dcm_CB = np.array([[0, 0, -1], - [0, 1, 0], - [1, 0, 0]]) + camera.wPixel = (17.3 * 1e-3) / 2048 + dcm_CB = np.array([[0, 0, -1], [0, 1, 0], [1, 0, 0]]) camera.dcm_CB = dcm_CB.tolist() for i in range(n_lmk): camera.addLandmark(pos_lmk[i, 0:3], normal_lmk[i, 0:3]) @@ -507,31 +570,43 @@ def run(show_plots, useBatch): pixelBatchLmk = np.zeros((n, n_lmk, 2)) # Process pinhole camera as a batch - camera.processBatch(r_BP_P, mrp_BP, -r_PN_P / np.linalg.norm(r_PN_P, axis=1)[:, None], False) + camera.processBatch( + r_BP_P, mrp_BP, -r_PN_P / np.linalg.norm(r_PN_P, axis=1)[:, None], False + ) isvisibleBatchLmk = np.array(camera.isvisibleBatchLmk) nvisibleBatchLmk = np.sum(isvisibleBatchLmk, axis=1) pixelBatchLmk[:, :, 0] = np.array(camera.pixelBatchLmk)[:, 0:n_lmk] - pixelBatchLmk[:, :, 1] = np.array(camera.pixelBatchLmk)[:, n_lmk:2*n_lmk] + pixelBatchLmk[:, :, 1] = np.array(camera.pixelBatchLmk)[:, n_lmk : 2 * n_lmk] # Ensure that results are equal as BSK sim - batch_diff = np.array([np.linalg.norm(nvisibleBatchLmk - nvisibleLmk) / np.linalg.norm(nvisibleLmk), - np.linalg.norm(isvisibleBatchLmk - isvisibleLmk) / np.linalg.norm(isvisibleLmk), - np.linalg.norm(pixelBatchLmk - pixelLmk) / np.linalg.norm(pixelLmk)]) + batch_diff = np.array( + [ + np.linalg.norm(nvisibleBatchLmk - nvisibleLmk) + / np.linalg.norm(nvisibleLmk), + np.linalg.norm(isvisibleBatchLmk - isvisibleLmk) + / np.linalg.norm(isvisibleLmk), + np.linalg.norm(pixelBatchLmk - pixelLmk) / np.linalg.norm(pixelLmk), + ] + ) else: batch_diff = 0 # Plot results of interest figureList = {} - plot_3D(t0, r0, np.array(vert_list), np.array(face_list), pos_lmk, isvisibleLmk0, dcm0) + plot_3D( + t0, r0, np.array(vert_list), np.array(face_list), pos_lmk, isvisibleLmk0, dcm0 + ) pltName = fileName + "1" figureList[pltName] = plt.figure(1) - plot_3D(tf, rf, np.array(vert_list), np.array(face_list), pos_lmk, isvisibleLmkf, dcmf) + plot_3D( + tf, rf, np.array(vert_list), np.array(face_list), pos_lmk, isvisibleLmkf, dcmf + ) pltName = fileName + "2" figureList[pltName] = plt.figure(2) - plot_pixel(pixelLmk[-1, : , :], isvisibleLmk[-1, :]) + plot_pixel(pixelLmk[-1, :, :], isvisibleLmk[-1, :]) pltName = fileName + "3" figureList[pltName] = plt.figure(3) @@ -558,6 +633,6 @@ def run(show_plots, useBatch): # if __name__ == "__main__": run( - True, # show_plots - False # useBatch + True, # show_plots + False, # useBatch ) diff --git a/examples/scenarioSmallBodyNavUKF.py b/examples/scenarioSmallBodyNavUKF.py index 03d932e9d0..d8b5ca3fdf 100644 --- a/examples/scenarioSmallBodyNavUKF.py +++ b/examples/scenarioSmallBodyNavUKF.py @@ -93,133 +93,139 @@ from Basilisk.simulation import simpleNav from Basilisk.simulation import spacecraft from Basilisk.utilities import RigidBodyKinematics -from Basilisk.utilities import (SimulationBaseClass, macros, simIncludeGravBody) +from Basilisk.utilities import SimulationBaseClass, macros, simIncludeGravBody from Basilisk.utilities import orbitalMotion from Basilisk.utilities import unitTestSupport +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile # The path to the location of Basilisk # Used to get the location of supporting data. fileName = os.path.basename(os.path.splitext(__file__)[0]) + # Plotting functions -def plot_3Dposition(r_truth, title='None'): +def plot_3Dposition(r_truth, title="None"): """Plot the relative position in 3D.""" fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.plot(r_truth[:, 0] / 1000., r_truth[:, 1]/1000., r_truth[:,2]/1000., 'b') - if title == 'inertial': - ax.set_xlabel('${}^{N}r_{x}$ [km]') - ax.set_ylabel('${}^{N}r_{y}$ [km]') - ax.set_zlabel('${}^{N}r_{z}$ [km]') - ax.set_title('Inertial frame') - elif title == 'asteroid': - ax.set_xlabel('${}^{A}r_{x}$ [km]') - ax.set_ylabel('${}^{A}r_{y}$ [km]') - ax.set_zlabel('${}^{A}r_{z}$ [km]') - ax.set_title('Small body fixed frame') + ax = fig.add_subplot(1, 1, 1, projection="3d") + ax.plot(r_truth[:, 0] / 1000.0, r_truth[:, 1] / 1000.0, r_truth[:, 2] / 1000.0, "b") + if title == "inertial": + ax.set_xlabel("${}^{N}r_{x}$ [km]") + ax.set_ylabel("${}^{N}r_{y}$ [km]") + ax.set_zlabel("${}^{N}r_{z}$ [km]") + ax.set_title("Inertial frame") + elif title == "asteroid": + ax.set_xlabel("${}^{A}r_{x}$ [km]") + ax.set_ylabel("${}^{A}r_{y}$ [km]") + ax.set_zlabel("${}^{A}r_{z}$ [km]") + ax.set_title("Small body fixed frame") + def plot_position(time, r_truth, r_est): """Plot the relative position result.""" - fig, ax = plt.subplots(3, sharex=True, figsize=(12,6)) + fig, ax = plt.subplots(3, sharex=True, figsize=(12, 6)) fig.add_subplot(111, frameon=False) - plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False) + plt.tick_params(labelcolor="none", top=False, bottom=False, left=False, right=False) - ax[0].plot(time/(3600*24), r_truth[:, 0]/1000, 'b', label='truth') - ax[1].plot(time/(3600*24), r_truth[:, 1]/1000, 'b') - ax[2].plot(time/(3600*24), r_truth[:, 2]/1000, 'b') + ax[0].plot(time / (3600 * 24), r_truth[:, 0] / 1000, "b", label="truth") + ax[1].plot(time / (3600 * 24), r_truth[:, 1] / 1000, "b") + ax[2].plot(time / (3600 * 24), r_truth[:, 2] / 1000, "b") - ax[0].plot(time/(3600*24), r_est[:, 0]/1000, color='orange', label='estimate') - ax[1].plot(time/(3600*24), r_est[:, 1]/1000, color='orange') - ax[2].plot(time/(3600*24), r_est[:, 2]/1000, color='orange') + ax[0].plot(time / (3600 * 24), r_est[:, 0] / 1000, color="orange", label="estimate") + ax[1].plot(time / (3600 * 24), r_est[:, 1] / 1000, color="orange") + ax[2].plot(time / (3600 * 24), r_est[:, 2] / 1000, color="orange") - plt.xlabel('Time [days]') - plt.title('Spacecraft Position') + plt.xlabel("Time [days]") + plt.title("Spacecraft Position") - ax[0].set_ylabel('${}^{A}r_{x}$ [km]') - ax[1].set_ylabel('${}^{A}r_{y}$ [km]') - ax[2].set_ylabel('${}^{A}r_{z}$ [km]') + ax[0].set_ylabel("${}^{A}r_{x}$ [km]") + ax[1].set_ylabel("${}^{A}r_{y}$ [km]") + ax[2].set_ylabel("${}^{A}r_{z}$ [km]") ax[0].legend() return + def plot_velocity(time, v_truth, v_est): """Plot the relative velocity result.""" plt.gcf() - fig, ax = plt.subplots(3, sharex=True, figsize=(12,6)) + fig, ax = plt.subplots(3, sharex=True, figsize=(12, 6)) fig.add_subplot(111, frameon=False) - plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False) + plt.tick_params(labelcolor="none", top=False, bottom=False, left=False, right=False) - ax[0].plot(time/(3600*24), v_truth[:, 0], 'b', label='truth') - ax[1].plot(time/(3600*24), v_truth[:, 1], 'b') - ax[2].plot(time/(3600*24), v_truth[:, 2], 'b') + ax[0].plot(time / (3600 * 24), v_truth[:, 0], "b", label="truth") + ax[1].plot(time / (3600 * 24), v_truth[:, 1], "b") + ax[2].plot(time / (3600 * 24), v_truth[:, 2], "b") - ax[0].plot(time/(3600*24), v_est[:, 0], color='orange', label='estimate') - ax[1].plot(time/(3600*24), v_est[:, 1], color='orange') - ax[2].plot(time/(3600*24), v_est[:, 2], color='orange') + ax[0].plot(time / (3600 * 24), v_est[:, 0], color="orange", label="estimate") + ax[1].plot(time / (3600 * 24), v_est[:, 1], color="orange") + ax[2].plot(time / (3600 * 24), v_est[:, 2], color="orange") - plt.xlabel('Time [days]') - plt.title('Spacecraft Velocity') + plt.xlabel("Time [days]") + plt.title("Spacecraft Velocity") - ax[0].set_ylabel('${}^{A}v_{x}$ [m/s]') - ax[1].set_ylabel('${}^{A}v_{y}$ [m/s]') - ax[2].set_ylabel('${}^{A}v_{z}$ [m/s]') + ax[0].set_ylabel("${}^{A}v_{x}$ [m/s]") + ax[1].set_ylabel("${}^{A}v_{y}$ [m/s]") + ax[2].set_ylabel("${}^{A}v_{z}$ [m/s]") ax[0].legend() return + def plot_acceleration(time, a_truth, a_est): """Plot the non-Keplerian acceleration result.""" plt.gcf() - fig, ax = plt.subplots(3, sharex=True, sharey=True, figsize=(12,6)) + fig, ax = plt.subplots(3, sharex=True, sharey=True, figsize=(12, 6)) fig.add_subplot(111, frameon=False) - plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False) + plt.tick_params(labelcolor="none", top=False, bottom=False, left=False, right=False) - ax[0].plot(time/(3600*24), a_truth[:, 0]*1000, 'b', label='truth') - ax[1].plot(time/(3600*24), a_truth[:, 1]*1000, 'b') - ax[2].plot(time/(3600*24), a_truth[:, 2]*1000, 'b') + ax[0].plot(time / (3600 * 24), a_truth[:, 0] * 1000, "b", label="truth") + ax[1].plot(time / (3600 * 24), a_truth[:, 1] * 1000, "b") + ax[2].plot(time / (3600 * 24), a_truth[:, 2] * 1000, "b") - ax[0].plot(time/(3600*24), a_est[:, 0]*1000, color='orange', label='estimate') - ax[1].plot(time/(3600*24), a_est[:, 1]*1000, color='orange') - ax[2].plot(time/(3600*24), a_est[:, 2]*1000, color='orange') + ax[0].plot(time / (3600 * 24), a_est[:, 0] * 1000, color="orange", label="estimate") + ax[1].plot(time / (3600 * 24), a_est[:, 1] * 1000, color="orange") + ax[2].plot(time / (3600 * 24), a_est[:, 2] * 1000, color="orange") - plt.xlabel('Time [days]') - plt.title('Inhomogeneous gravity acceleration') + plt.xlabel("Time [days]") + plt.title("Inhomogeneous gravity acceleration") - ax[0].set_ylabel('${}^{A}a_{x}$ [mm/s$^2$]') - ax[1].set_ylabel('${}^{A}a_{y}$ [mm/s$^2$]') - ax[2].set_ylabel('${}^{A}a_{z}$ [mm/s$^2$]') + ax[0].set_ylabel("${}^{A}a_{x}$ [mm/s$^2$]") + ax[1].set_ylabel("${}^{A}a_{y}$ [mm/s$^2$]") + ax[2].set_ylabel("${}^{A}a_{z}$ [mm/s$^2$]") ax[0].legend() return + def plot_pos_error(time, r_err, P): """Plot the position estimation error and associated covariance.""" plt.gcf() - fig, ax = plt.subplots(3, sharex=True, figsize=(12,6)) + fig, ax = plt.subplots(3, sharex=True, figsize=(12, 6)) fig.add_subplot(111, frameon=False) - plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False) + plt.tick_params(labelcolor="none", top=False, bottom=False, left=False, right=False) - ax[0].plot(time/(3600*24), r_err[:, 0], 'b', label='error') - ax[0].plot(time/(3600*24), 2*np.sqrt(P[:, 0, 0]), 'k--', label=r'$2\sigma$') - ax[0].plot(time/(3600*24), -2*np.sqrt(P[:, 0, 0]), 'k--') + ax[0].plot(time / (3600 * 24), r_err[:, 0], "b", label="error") + ax[0].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 0, 0]), "k--", label=r"$2\sigma$") + ax[0].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 0, 0]), "k--") - ax[1].plot(time/(3600*24), r_err[:, 1], 'b') - ax[1].plot(time/(3600*24), 2*np.sqrt(P[:, 1, 1]), 'k--') - ax[1].plot(time/(3600*24), -2*np.sqrt(P[:, 1, 1]), 'k--') + ax[1].plot(time / (3600 * 24), r_err[:, 1], "b") + ax[1].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 1, 1]), "k--") + ax[1].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 1, 1]), "k--") - ax[2].plot(time/(3600*24), r_err[:, 2], 'b') - ax[2].plot(time/(3600*24), 2*np.sqrt(P[:, 2, 2]), 'k--') - ax[2].plot(time/(3600*24), -2*np.sqrt(P[:, 2, 2]), 'k--') + ax[2].plot(time / (3600 * 24), r_err[:, 2], "b") + ax[2].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 2, 2]), "k--") + ax[2].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 2, 2]), "k--") - plt.xlabel('Time [days]') - plt.title('Position Error and Covariance') + plt.xlabel("Time [days]") + plt.title("Position Error and Covariance") - ax[0].set_ylabel('${}^{A}r_{x}$ [m]') - ax[1].set_ylabel('${}^{A}r_{y}$ [m]') - ax[2].set_ylabel('${}^{A}r_{z}$ [m]') + ax[0].set_ylabel("${}^{A}r_{x}$ [m]") + ax[1].set_ylabel("${}^{A}r_{y}$ [m]") + ax[2].set_ylabel("${}^{A}r_{z}$ [m]") ax[0].legend() @@ -229,58 +235,61 @@ def plot_pos_error(time, r_err, P): def plot_vel_error(time, v_err, P): """Plot the velocity estimation error and associated covariance.""" plt.gcf() - fig, ax = plt.subplots(3, sharex=True, sharey=True, figsize=(12,6)) + fig, ax = plt.subplots(3, sharex=True, sharey=True, figsize=(12, 6)) fig.add_subplot(111, frameon=False) - plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False) + plt.tick_params(labelcolor="none", top=False, bottom=False, left=False, right=False) - ax[0].plot(time/(3600*24), v_err[:, 0], 'b', label='error') - ax[0].plot(time/(3600*24), 2*np.sqrt(P[:, 3, 3]), 'k--', label=r'$2\sigma$') - ax[0].plot(time/(3600*24), -2*np.sqrt(P[:, 3, 3]), 'k--') + ax[0].plot(time / (3600 * 24), v_err[:, 0], "b", label="error") + ax[0].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 3, 3]), "k--", label=r"$2\sigma$") + ax[0].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 3, 3]), "k--") - ax[1].plot(time/(3600*24), v_err[:, 1], 'b') - ax[1].plot(time/(3600*24), 2*np.sqrt(P[:, 4, 4]), 'k--') - ax[1].plot(time/(3600*24), -2*np.sqrt(P[:, 4, 4]), 'k--') + ax[1].plot(time / (3600 * 24), v_err[:, 1], "b") + ax[1].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 4, 4]), "k--") + ax[1].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 4, 4]), "k--") - ax[2].plot(time/(3600*24), v_err[:, 2], 'b') - ax[2].plot(time/(3600*24), 2*np.sqrt(P[:, 5, 5]), 'k--') - ax[2].plot(time/(3600*24), -2*np.sqrt(P[:, 5, 5]), 'k--') + ax[2].plot(time / (3600 * 24), v_err[:, 2], "b") + ax[2].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 5, 5]), "k--") + ax[2].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 5, 5]), "k--") - plt.xlabel('Time [days]') - plt.title('Velocity Error and Covariance') + plt.xlabel("Time [days]") + plt.title("Velocity Error and Covariance") - ax[0].set_ylabel('${}^{A}v_{x}$ [m/s]') - ax[1].set_ylabel('${}^{A}v_{y}$ [m/s]') - ax[2].set_ylabel('${}^{A}v_{z}$ [m/s]') + ax[0].set_ylabel("${}^{A}v_{x}$ [m/s]") + ax[1].set_ylabel("${}^{A}v_{y}$ [m/s]") + ax[2].set_ylabel("${}^{A}v_{z}$ [m/s]") ax[0].legend() return + def plot_acc_error(time, a_err, P): """Plot the non-Keplerian acceleration estimation error and associated covariance.""" plt.gcf() - fig, ax = plt.subplots(3, sharex=True, sharey=True, figsize=(12,6)) + fig, ax = plt.subplots(3, sharex=True, sharey=True, figsize=(12, 6)) fig.add_subplot(111, frameon=False) - plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False) + plt.tick_params(labelcolor="none", top=False, bottom=False, left=False, right=False) - ax[0].plot(time/(3600*24), a_err[:, 0]*1000, 'b', label='error') - ax[0].plot(time/(3600*24), 2*np.sqrt(P[:, 6, 6])*1000, 'k--', label=r'$2\sigma$') - ax[0].plot(time/(3600*24), -2*np.sqrt(P[:, 6, 6])*1000, 'k--') + ax[0].plot(time / (3600 * 24), a_err[:, 0] * 1000, "b", label="error") + ax[0].plot( + time / (3600 * 24), 2 * np.sqrt(P[:, 6, 6]) * 1000, "k--", label=r"$2\sigma$" + ) + ax[0].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 6, 6]) * 1000, "k--") - ax[1].plot(time/(3600*24), a_err[:, 1]*1000, 'b') - ax[1].plot(time/(3600*24), 2*np.sqrt(P[:, 7, 7])*1000, 'k--') - ax[1].plot(time/(3600*24), -2*np.sqrt(P[:, 7, 7])*1000, 'k--') + ax[1].plot(time / (3600 * 24), a_err[:, 1] * 1000, "b") + ax[1].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 7, 7]) * 1000, "k--") + ax[1].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 7, 7]) * 1000, "k--") - ax[2].plot(time/(3600*24), a_err[:, 2]*1000, 'b') - ax[2].plot(time/(3600*24), 2*np.sqrt(P[:, 8, 8])*1000, 'k--') - ax[2].plot(time/(3600*24), -2*np.sqrt(P[:, 8, 8])*1000, 'k--') + ax[2].plot(time / (3600 * 24), a_err[:, 2] * 1000, "b") + ax[2].plot(time / (3600 * 24), 2 * np.sqrt(P[:, 8, 8]) * 1000, "k--") + ax[2].plot(time / (3600 * 24), -2 * np.sqrt(P[:, 8, 8]) * 1000, "k--") - plt.xlabel('Time [days]') - plt.title('Acceleration Error and Covariance') + plt.xlabel("Time [days]") + plt.title("Acceleration Error and Covariance") - ax[0].set_ylabel('${}^{A}a_{x}$ [mm/s$^2$]') - ax[1].set_ylabel('${}^{A}a_{y}$ [mm/s$^2$]') - ax[2].set_ylabel('${}^{A}a_{z}$ [mm/s$^2$]') + ax[0].set_ylabel("${}^{A}a_{x}$ [mm/s$^2$]") + ax[1].set_ylabel("${}^{A}a_{y}$ [mm/s$^2$]") + ax[2].set_ylabel("${}^{A}a_{z}$ [mm/s$^2$]") ax[0].legend() @@ -289,6 +298,7 @@ def plot_acc_error(time, a_err, P): def run(show_plots): from Basilisk import __path__ + bskPath = __path__[0] fileName = os.path.basename(os.path.splitext(__file__)[0]) @@ -304,12 +314,12 @@ def run(show_plots): # create the dynamics task and specify the simulation time step information simulationTimeStep = macros.sec2nano(15) - simulationTime = macros.sec2nano(4*24*3600.0) + simulationTime = macros.sec2nano(4 * 24 * 3600.0) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # setup celestial object ephemeris module gravBodyEphem = planetEphemeris.PlanetEphemeris() - gravBodyEphem.ModelTag = 'vestaEphemeris' + gravBodyEphem.ModelTag = "vestaEphemeris" gravBodyEphem.setPlanetNames(planetEphemeris.StringVector(["vesta"])) # specify small body o.e. and rotational state January 21st, 2022 @@ -317,10 +327,10 @@ def run(show_plots): oeAsteroid = planetEphemeris.ClassicElements() oeAsteroid.a = 2.3612 * orbitalMotion.AU * 1000 # meters oeAsteroid.e = 0.08823 - oeAsteroid.i = 7.1417*macros.D2R - oeAsteroid.Omega = 103.8*macros.D2R - oeAsteroid.omega = 151.1*macros.D2R - oeAsteroid.f = 7.0315*macros.D2R + oeAsteroid.i = 7.1417 * macros.D2R + oeAsteroid.Omega = 103.8 * macros.D2R + oeAsteroid.omega = 151.1 * macros.D2R + oeAsteroid.f = 7.0315 * macros.D2R # the rotational state would be prescribed to AR = 309.03 * macros.D2R @@ -330,21 +340,24 @@ def run(show_plots): gravBodyEphem.rightAscension = planetEphemeris.DoubleVector([AR]) gravBodyEphem.declination = planetEphemeris.DoubleVector([dec]) gravBodyEphem.lst0 = planetEphemeris.DoubleVector([lst0]) - gravBodyEphem.rotRate = planetEphemeris.DoubleVector([360 * macros.D2R / (5.3421 * 3600.)]) + gravBodyEphem.rotRate = planetEphemeris.DoubleVector( + [360 * macros.D2R / (5.3421 * 3600.0)] + ) # initialize small body fixed frame dcm w.r.t. inertial and its angular velocity - dcm_AN = RigidBodyKinematics.euler3232C([AR , np.pi/2 - dec, lst0]) - omega_AN_A = np.array([0,0,360 * macros.D2R / (5.3421 * 3600.)]) + dcm_AN = RigidBodyKinematics.euler3232C([AR, np.pi / 2 - dec, lst0]) + omega_AN_A = np.array([0, 0, 360 * macros.D2R / (5.3421 * 3600.0)]) # setup small body gravity effector (no Sun 3rd perturbation included) # https://ssd.jpl.nasa.gov/tools/gravity.html#/vesta gravFactory = simIncludeGravBody.gravBodyFactory() - mu = 17.2882449693*1e9 # m^3/s^2 - asteroid = gravFactory.createCustomGravObject("vesta", mu, radEquator=265*1000) + mu = 17.2882449693 * 1e9 # m^3/s^2 + asteroid = gravFactory.createCustomGravObject("vesta", mu, radEquator=265 * 1000) asteroid.isCentralBody = True nSpherHarm = 14 - asteroid.useSphericalHarmonicsGravityModel(bskPath + "/supportData/LocalGravData/VESTA20H.txt", nSpherHarm) + vesta_path = get_path(DataFile.LocalGravData.VESTA20H) + asteroid.useSphericalHarmonicsGravityModel(str(vesta_path), nSpherHarm) asteroid.planetBodyInMsg.subscribeTo(gravBodyEphem.planetOutMsgs[0]) # create an ephemeris converter @@ -359,7 +372,7 @@ def run(show_plots): # setup orbit initial conditions of the sc oe = orbitalMotion.ClassicElements() - oe.a = 500*1000 # meters + oe.a = 500 * 1000 # meters oe.e = 0.001 oe.i = 175 * macros.D2R oe.Omega = 48.2 * macros.D2R @@ -373,7 +386,7 @@ def run(show_plots): # set up simpleNav for s/c "measurements" simpleNavMeas = simpleNav.SimpleNav() - simpleNavMeas.ModelTag = 'SimpleNav' + simpleNavMeas.ModelTag = "SimpleNav" simpleNavMeas.scStateInMsg.subscribeTo(scObject.scStateOutMsg) pos_sigma_sc = 10.0 vel_sigma_sc = 0.1 @@ -381,25 +394,388 @@ def run(show_plots): rate_sigma_sc = 0.0 * math.pi / 180.0 sun_sigma_sc = 0.0 dv_sigma_sc = 0.0 - p_matrix_sc = [[pos_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., pos_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., pos_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., vel_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., vel_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., vel_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., att_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., att_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., att_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., rate_sigma_sc, 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., rate_sigma_sc, 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., rate_sigma_sc, 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., sun_sigma_sc, 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., sun_sigma_sc, 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., sun_sigma_sc, 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., dv_sigma_sc, 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., dv_sigma_sc, 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., dv_sigma_sc]] - walk_bounds_sc = [[10.], [10.], [10.], [0.1], [0.1], [0.1], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.]] + p_matrix_sc = [ + [ + pos_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + pos_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + pos_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + vel_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + vel_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + vel_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + att_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + att_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + att_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + rate_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + rate_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + rate_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + sun_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + sun_sigma_sc, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + sun_sigma_sc, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + dv_sigma_sc, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + dv_sigma_sc, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + dv_sigma_sc, + ], + ] + walk_bounds_sc = [ + [10.0], + [10.0], + [10.0], + [0.1], + [0.1], + [0.1], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + ] simpleNavMeas.PMatrix = p_matrix_sc simpleNavMeas.walkBounds = walk_bounds_sc @@ -412,19 +788,34 @@ def run(show_plots): vel_sigma_p = 0.0 att_sigma_p = 0 * math.pi / 180.0 rate_sigma_p = 0 * math.pi / 180.0 - p_matrix_p = [[pos_sigma_p, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., pos_sigma_p, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., pos_sigma_p, 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., vel_sigma_p, 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., vel_sigma_p, 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., vel_sigma_p, 0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., att_sigma_p, 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., att_sigma_p, 0., 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., att_sigma_p, 0., 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., rate_sigma_p, 0., 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., rate_sigma_p, 0.], - [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., rate_sigma_p]] - walk_bounds_p = [[0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.]] + p_matrix_p = [ + [pos_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, pos_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, pos_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, vel_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, vel_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, vel_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, att_sigma_p, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, att_sigma_p, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, att_sigma_p, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, rate_sigma_p, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, rate_sigma_p, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, rate_sigma_p], + ] + walk_bounds_p = [ + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + ] planetNavMeas.PMatrix = p_matrix_p planetNavMeas.walkBounds = walk_bounds_p @@ -439,41 +830,45 @@ def run(show_plots): P_proc_pos = 1000 P_proc_vel = 1 P_proc_acc = 1e-6 - P_proc = [[P_proc_pos, 0., 0., 0., 0., 0., 0., 0., 0.], - [0., P_proc_pos, 0., 0., 0., 0., 0., 0., 0.], - [0., 0., P_proc_pos, 0., 0., 0., 0., 0., 0.], - [0., 0., 0., P_proc_vel, 0., 0., 0., 0., 0.], - [0., 0., 0., 0., P_proc_vel, 0., 0., 0., 0.], - [0., 0., 0., 0., 0., P_proc_vel, 0., 0., 0.], - [0., 0., 0., 0., 0., 0., P_proc_acc, 0., 0.], - [0., 0., 0., 0., 0., 0., 0., P_proc_acc, 0.], - [0., 0., 0., 0., 0., 0., 0., 0., P_proc_acc]] + P_proc = [ + [P_proc_pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, P_proc_pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, P_proc_pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, P_proc_vel, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, P_proc_vel, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, P_proc_vel, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, P_proc_acc, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, P_proc_acc, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, P_proc_acc], + ] smallBodyNav.P_proc = P_proc # set the measurement noise R_meas = np.identity(3) - R_meas[0,0] = R_meas[1,1] = R_meas[2,2] = 100 # position sigmas + R_meas[0, 0] = R_meas[1, 1] = R_meas[2, 2] = 100 # position sigmas smallBodyNav.R_meas = R_meas.tolist() # Measurement Noise # set the initial guess, x_0 x_0 = np.zeros(9) x_0[0:3] = r_CA_A - x_0[3:6] = v_CA_A - np.cross(omega_AN_A,r_CA_A) + x_0[3:6] = v_CA_A - np.cross(omega_AN_A, r_CA_A) smallBodyNav.x_hat_k = x_0 # set the initial state covariance P_k_pos = 1e4 P_k_vel = 0.1 P_k_acc = 1e-4 - P_k = [[P_k_pos, 0., 0., 0., 0., 0., 0., 0., 0.], - [0., P_k_pos, 0., 0., 0., 0., 0., 0., 0.], - [0., 0., P_k_pos, 0., 0., 0., 0., 0., 0.], - [0., 0., 0., P_k_vel, 0., 0., 0., 0., 0.], - [0., 0., 0., 0., P_k_vel, 0., 0., 0., 0.], - [0., 0., 0., 0., 0., P_k_vel, 0., 0., 0.], - [0., 0., 0., 0., 0., 0., P_k_acc, 0., 0.], - [0., 0., 0., 0., 0., 0., 0., P_k_acc, 0.], - [0., 0., 0., 0., 0., 0., 0., 0., P_k_acc]] + P_k = [ + [P_k_pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, P_k_pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, P_k_pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, P_k_vel, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, P_k_vel, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, P_k_vel, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, P_k_acc, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, P_k_acc, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, P_k_acc], + ] smallBodyNav.P_k = P_k # set UKF hyperparameters @@ -527,8 +922,8 @@ def run(show_plots): omega_AN_A = ast_truth_recorder.omega_BN_B # initialize truth inertial position and velocity w.r.t. asteroid centered fixed frame - r_truth_A = np.zeros((N_points,3)) - v_truth_A = np.zeros((N_points,3)) + r_truth_A = np.zeros((N_points, 3)) + v_truth_A = np.zeros((N_points, 3)) a_truth_A = np.zeros((N_points, 3)) # loop through simulation points @@ -538,10 +933,14 @@ def run(show_plots): # rotate position and velocity r_truth_A[ii][0:3] = R_AN.dot(r_truth_N[ii][0:3]) - v_truth_A[ii][0:3] = R_AN.dot(v_truth_N[ii][0:3]) - np.cross(omega_AN_A[ii][0:3], r_truth_A[ii][0:3]) + v_truth_A[ii][0:3] = R_AN.dot(v_truth_N[ii][0:3]) - np.cross( + omega_AN_A[ii][0:3], r_truth_A[ii][0:3] + ) # compute gravity acceleration and substract Keplerian term - a_truth_A[ii][0:3] = np.array(asteroid.spherHarm.computeField(r_truth_A[ii][0:3], nSpherHarm, False)).reshape(3) + a_truth_A[ii][0:3] = np.array( + asteroid.spherHarm.computeField(r_truth_A[ii][0:3], nSpherHarm, False) + ).reshape(3) # get filter output x_hat = sc_nav_recorder.state @@ -550,46 +949,47 @@ def run(show_plots): # plot the results figureList = {} - plot_3Dposition(r_truth_N, title='inertial') + plot_3Dposition(r_truth_N, title="inertial") pltName = fileName + "1" figureList[pltName] = plt.figure(1) - plot_3Dposition(r_truth_A, title='asteroid') + plot_3Dposition(r_truth_A, title="asteroid") pltName = fileName + "2" figureList[pltName] = plt.figure(2) - plot_position(time, r_truth_A, x_hat[:,0:3]) + plot_position(time, r_truth_A, x_hat[:, 0:3]) pltName = fileName + "3" figureList[pltName] = plt.figure(3) - plot_velocity(time, v_truth_A, x_hat[:,3:6]) + plot_velocity(time, v_truth_A, x_hat[:, 3:6]) pltName = fileName + "4" figureList[pltName] = plt.figure(4) - plot_acceleration(time, a_truth_A, x_hat[:,6:9]) + plot_acceleration(time, a_truth_A, x_hat[:, 6:9]) pltName = fileName + "5" figureList[pltName] = plt.figure(5) - plot_pos_error(time, np.subtract(r_truth_A, x_hat[:,0:3]), P) + plot_pos_error(time, np.subtract(r_truth_A, x_hat[:, 0:3]), P) pltName = fileName + "6" figureList[pltName] = plt.figure(6) - plot_vel_error(time, np.subtract(v_truth_A, x_hat[:,3:6]), P) + plot_vel_error(time, np.subtract(v_truth_A, x_hat[:, 3:6]), P) pltName = fileName + "7" figureList[pltName] = plt.figure(7) - plot_acc_error(time, np.subtract(a_truth_A,x_hat[:,6:9]), P) + plot_acc_error(time, np.subtract(a_truth_A, x_hat[:, 6:9]), P) pltName = fileName + "8" figureList[pltName] = plt.figure(8) if show_plots: - plt.show() + plt.show() # close the plots being saved off to avoid over-writing old and new figures plt.close("all") return figureList + # # This statement below ensures that the unit test script can be run as a # stand-along python script diff --git a/examples/scenarioSpiceSpacecraft.py b/examples/scenarioSpiceSpacecraft.py index f453751afd..00ee4e87b0 100755 --- a/examples/scenarioSpiceSpacecraft.py +++ b/examples/scenarioSpiceSpacecraft.py @@ -81,9 +81,12 @@ # import general simulation support files from Basilisk.utilities import SimulationBaseClass -from Basilisk.utilities import unitTestSupport # general support file with common unit test functions +from Basilisk.utilities import ( + unitTestSupport, +) # general support file with common unit test functions import matplotlib.pyplot as plt from Basilisk.utilities import macros +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile # import simulation related support from Basilisk.simulation import spacecraft @@ -106,6 +109,7 @@ # The path to the location of Basilisk # Used to get the location of supporting data. from Basilisk import __path__ + bskPath = __path__[0] fileName = os.path.basename(os.path.splitext(__file__)[0]) @@ -127,7 +131,7 @@ def run(show_plots): scSim = SimulationBaseClass.SimBaseClass() # set the simulation time variable used later on - simulationTime = macros.min2nano(10.) + simulationTime = macros.min2nano(10.0) # # create the simulation process @@ -135,7 +139,7 @@ def run(show_plots): dynProcess = scSim.CreateNewProcess(simProcessName) # create the dynamics task and specify the integration update time - simulationTimeStep = macros.sec2nano(.1) + simulationTimeStep = macros.sec2nano(0.1) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # @@ -146,9 +150,7 @@ def run(show_plots): scObject = spacecraft.Spacecraft() scObject.ModelTag = "Hubble" # define the simulation inertia - I = [900., 0., 0., - 0., 800., 0., - 0., 0., 600.] + I = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0] scObject.hub.mHub = 750.0 # kg - spacecraft mass scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) @@ -168,10 +170,11 @@ def run(show_plots): # setup spice library for Earth ephemeris and Hubble states timeInitString = "2015 February 10, 00:00:00.0 TDB" spiceObject = gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) - spiceObject.zeroBase = 'Earth' + spiceObject.zeroBase = "Earth" scNames = ["HUBBLE SPACE TELESCOPE"] spiceObject.addSpacecraftNames(messaging.StringVector(scNames)) - spiceObject.loadSpiceKernel("hst_edited.bsp", bskPath + '/supportData/EphemerisData/') + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + spiceObject.loadSpiceKernel(str(hst_edited_path), "") # need spice to run before spacecraft module as it provides the spacecraft translational states scSim.AddModelToTask(simTaskName, spiceObject) @@ -198,7 +201,7 @@ def run(show_plots): inertial3DObj = inertial3D.inertial3D() inertial3DObj.ModelTag = "inertial3D" scSim.AddModelToTask(simTaskName, inertial3DObj) - inertial3DObj.sigma_R0N = [0., 0., 0.] # set the desired inertial orientation + inertial3DObj.sigma_R0N = [0.0, 0.0, 0.0] # set the desired inertial orientation # setup the attitude tracking error evaluation module attError = attTrackingError.attTrackingError() @@ -212,7 +215,7 @@ def run(show_plots): mrpControl.K = 3.5 mrpControl.Ki = -1 # make value negative to turn off integral feedback mrpControl.P = 30.0 - mrpControl.integralLimit = 2. / mrpControl.Ki * 0.1 + mrpControl.integralLimit = 2.0 / mrpControl.Ki * 0.1 # # create simulation messages @@ -238,7 +241,9 @@ def run(show_plots): # Setup data logging before the simulation is initialized # numDataPoints = 100 - samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) + samplingTime = unitTestSupport.samplingTime( + simulationTime, simulationTimeStep, numDataPoints + ) snLog = sNavObject.scStateInMsg.recorder(samplingTime) attErrorLog = attError.attGuidOutMsg.recorder(samplingTime) mrpLog = mrpControl.cmdTorqueOutMsg.recorder(samplingTime) @@ -253,9 +258,12 @@ def run(show_plots): scObject.hub.omega_BN_BInit = [[0.001], [-0.01], [0.03]] # rad/s - omega_BN_B # if this scenario is to interface with the BSK Viz, uncomment the following line - vizSupport.enableUnityVisualization(scSim, simTaskName, scObject - # , saveFile=fileName - ) + vizSupport.enableUnityVisualization( + scSim, + simTaskName, + scObject, + # , saveFile=fileName + ) # # initialize Simulation @@ -270,7 +278,7 @@ def run(show_plots): # unload custom Spice kernel gravFactory.unloadSpiceKernels() - spiceObject.unloadSpiceKernel("hst_edited.bsp", bskPath + '/supportData/EphemerisData/') + spiceObject.unloadSpiceKernel(str(hst_edited_path), "") # # plot the results @@ -279,43 +287,55 @@ def run(show_plots): plt.close("all") # clears out plots from earlier test runs plt.figure(1) for idx in range(3): - plt.plot(timeAxis * macros.NANO2MIN, attErrorLog.sigma_BR[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\sigma_' + str(idx) + '$') - plt.legend(loc='lower right') - plt.xlabel('Time [min]') - plt.ylabel(r'Attitude Error $\sigma_{B/R}$') + plt.plot( + timeAxis * macros.NANO2MIN, + attErrorLog.sigma_BR[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\sigma_" + str(idx) + "$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [min]") + plt.ylabel(r"Attitude Error $\sigma_{B/R}$") figureList = {} pltName = fileName + "1" figureList[pltName] = plt.figure(1) plt.figure(2) for idx in range(3): - plt.plot(timeAxis * macros.NANO2MIN, mrpLog.torqueRequestBody[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label='$L_{r,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [min]') - plt.ylabel(r'Control Torque $L_r$ [Nm]') + plt.plot( + timeAxis * macros.NANO2MIN, + mrpLog.torqueRequestBody[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label="$L_{r," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [min]") + plt.ylabel(r"Control Torque $L_r$ [Nm]") pltName = fileName + "2" plt.figure(3) for idx in range(3): - plt.plot(timeAxis * macros.NANO2MIN, attErrorLog.omega_BR_B[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - label=r'$\omega_{BR,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [min]') - plt.ylabel('Rate Tracking Error [rad/s] ') + plt.plot( + timeAxis * macros.NANO2MIN, + attErrorLog.omega_BR_B[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + label=r"$\omega_{BR," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [min]") + plt.ylabel("Rate Tracking Error [rad/s] ") plt.figure(4) for idx in range(3): - plt.plot(timeAxis * macros.NANO2MIN, snLog.r_BN_N[:, idx] / 1000., - color=unitTestSupport.getLineColor(idx, 3), - label='$r_{BN,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [min]') - plt.ylabel('Inertial Position [km]') + plt.plot( + timeAxis * macros.NANO2MIN, + snLog.r_BN_N[:, idx] / 1000.0, + color=unitTestSupport.getLineColor(idx, 3), + label="$r_{BN," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [min]") + plt.ylabel("Inertial Position [km]") figureList[pltName] = plt.figure(4) if show_plots: diff --git a/src/fswAlgorithms/transDetermination/chebyPosEphem/_UnitTest/test_chebyPosEphem.py b/src/fswAlgorithms/transDetermination/chebyPosEphem/_UnitTest/test_chebyPosEphem.py index 999cd4bb1f..b3e4297a89 100644 --- a/src/fswAlgorithms/transDetermination/chebyPosEphem/_UnitTest/test_chebyPosEphem.py +++ b/src/fswAlgorithms/transDetermination/chebyPosEphem/_UnitTest/test_chebyPosEphem.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -26,6 +25,7 @@ filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) from Basilisk import __path__ + bskPath = __path__[0] from Basilisk.utilities import SimulationBaseClass @@ -33,20 +33,21 @@ from Basilisk.fswAlgorithms import chebyPosEphem from Basilisk.topLevelModules import pyswice from Basilisk.utilities.pyswice_spk_utilities import spkRead +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + import matplotlib.pyplot as plt from Basilisk.architecture import messaging orbitPosAccuracy = 1.0 orbitVelAccuracy = 0.01 + # uncomment this line is this test is to be skipped in the global unit test run, adjust message as needed # @pytest.mark.skipif(conditionstring) # uncomment this line if this test has an expected failure, adjust message as needed # @pytest.mark.xfail() # need to update how the RW states are defined # provide a unique test method name, starting with test_ -@pytest.mark.parametrize("function", ["sineCosine" - , "earthOrbitFit" - ]) +@pytest.mark.parametrize("function", ["sineCosine", "earthOrbitFit"]) def test_chebyPosFitAllTest(show_plots, function): """Module Unit Test""" testFunction = globals().get(function) @@ -69,26 +70,31 @@ def sineCosine(show_plots): testMessages = [] # create empty list to store test log messages orbitRadius = 70000.0 - numCurvePoints = 365*3+1 - curveDurationDays = 365.0*3 - degChebCoeff =21 + numCurvePoints = 365 * 3 + 1 + curveDurationDays = 365.0 * 3 + degChebCoeff = 21 - angleSpace = numpy.linspace(-3*math.pi, 3*math.pi, numCurvePoints) + angleSpace = numpy.linspace(-3 * math.pi, 3 * math.pi, numCurvePoints) - cosineValues = numpy.cos(angleSpace)*orbitRadius - sineValues = numpy.sin(angleSpace)*orbitRadius + cosineValues = numpy.cos(angleSpace) * orbitRadius + sineValues = numpy.sin(angleSpace) * orbitRadius oopValues = numpy.sin(angleSpace) + orbitRadius - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + pyswice.furnsh_c(str(naif0012_path)) et = pyswice.new_doubleArray(1) - timeStringMid = '2019 APR 1 12:12:12.0 (UTC)' + timeStringMid = "2019 APR 1 12:12:12.0 (UTC)" pyswice.str2et_c(timeStringMid, et) fitTimes = numpy.linspace(-1, 1, numCurvePoints) - chebCosCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, cosineValues, degChebCoeff) - chebSinCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, sineValues, degChebCoeff) + chebCosCoeff = numpy.polynomial.chebyshev.chebfit( + fitTimes, cosineValues, degChebCoeff + ) + chebSinCoeff = numpy.polynomial.chebyshev.chebfit( + fitTimes, sineValues, degChebCoeff + ) cheboopCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, oopValues, degChebCoeff) unitTaskName = "unitTask" # arbitrary name (don't change) @@ -99,7 +105,9 @@ def sineCosine(show_plots): FSWUnitTestProc = TotalSim.CreateNewProcess(unitProcessName) # create the dynamics task and specify the integration update time - FSWUnitTestProc.addTask(TotalSim.CreateNewTask(unitTaskName, macros.sec2nano(8640.0))) + FSWUnitTestProc.addTask( + TotalSim.CreateNewTask(unitTaskName, macros.sec2nano(8640.0)) + ) chebyFitModel = chebyPosEphem.chebyPosEphem() chebyFitModel.ModelTag = "chebyFitModel" @@ -110,14 +118,15 @@ def sineCosine(show_plots): totalList.extend(numpy.array(cheboopCoeff).tolist()) chebyFitModel.ephArray[0].posChebyCoeff = totalList - chebyFitModel.ephArray[0].nChebCoeff = degChebCoeff+1 + chebyFitModel.ephArray[0].nChebCoeff = degChebCoeff + 1 chebyFitModel.ephArray[0].ephemTimeMid = pyswice.doubleArray_getitem(et, 0) - chebyFitModel.ephArray[0].ephemTimeRad = curveDurationDays/2.0*86400.0 + chebyFitModel.ephArray[0].ephemTimeRad = curveDurationDays / 2.0 * 86400.0 clockCorrData = messaging.TDBVehicleClockCorrelationMsgPayload() clockCorrData.vehicleClockTime = 0.0 - clockCorrData.ephemerisTime = chebyFitModel.ephArray[0].ephemTimeMid - \ - chebyFitModel.ephArray[0].ephemTimeRad + clockCorrData.ephemerisTime = ( + chebyFitModel.ephArray[0].ephemTimeMid - chebyFitModel.ephArray[0].ephemTimeRad + ) clockInMsg = messaging.TDBVehicleClockCorrelationMsg().write(clockCorrData) chebyFitModel.clockCorrInMsg.subscribeTo(clockInMsg) @@ -127,65 +136,76 @@ def sineCosine(show_plots): TotalSim.AddModelToTask(unitTaskName, dataLog) TotalSim.InitializeSimulation() - TotalSim.ConfigureStopTime(int(curveDurationDays*86400.0*1.0E9)) + TotalSim.ConfigureStopTime(int(curveDurationDays * 86400.0 * 1.0e9)) TotalSim.ExecuteSimulation() posChebData = dataLog.r_BdyZero_N - angleSpaceFine = numpy.linspace(-3*math.pi, 3*math.pi, numCurvePoints*10-9) + angleSpaceFine = numpy.linspace(-3 * math.pi, 3 * math.pi, numCurvePoints * 10 - 9) - cosineValuesFine = numpy.cos(angleSpaceFine)*orbitRadius - sineValuesFine = numpy.sin(angleSpaceFine)*orbitRadius + cosineValuesFine = numpy.cos(angleSpaceFine) * orbitRadius + sineValuesFine = numpy.sin(angleSpaceFine) * orbitRadius oopValuesFine = numpy.sin(angleSpaceFine) + orbitRadius - maxErrVec = [max(abs(posChebData[:,0] - cosineValuesFine)), - max(abs(posChebData[:,1] - sineValuesFine)), - max(abs(posChebData[:,2] - oopValuesFine))] + maxErrVec = [ + max(abs(posChebData[:, 0] - cosineValuesFine)), + max(abs(posChebData[:, 1] - sineValuesFine)), + max(abs(posChebData[:, 2] - oopValuesFine)), + ] - print("Sine Wave error: " + str(max(maxErrVec))) + print("Sine Wave error: " + str(max(maxErrVec))) assert max(maxErrVec) < orbitPosAccuracy if testFailCount == 0: print("PASSED: " + " Sine and Cosine curve fit") # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] + def earthOrbitFit(show_plots): # The __tracebackhide__ setting influences pytest showing of tracebacks: # the mrp_steering_tracking() function will not be shown unless the # --fulltrace command line option is specified. - #__tracebackhide__ = True + # __tracebackhide__ = True testFailCount = 0 # zero unit test result counter testMessages = [] # create empty list to store test log messages - numCurvePoints = 365*3+1 - curveDurationSeconds = 3*5950.0 - degChebCoeff =23 + numCurvePoints = 365 * 3 + 1 + curveDurationSeconds = 3 * 5950.0 + degChebCoeff = 23 integFrame = "j2000" zeroBase = "Earth" dateSpice = "2015 February 10, 00:00:00.0 TDB" - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + pyswice.furnsh_c(str(naif0012_path)) et = pyswice.new_doubleArray(1) pyswice.str2et_c(dateSpice, et) etStart = pyswice.doubleArray_getitem(et, 0) etEnd = etStart + curveDurationSeconds - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.furnsh_c(path + '/hst_edited.bsp') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(naif0012_path)) + pyswice.furnsh_c(str(de403masses_path)) + pyswice.furnsh_c(str(pck00010_path)) + pyswice.furnsh_c(str(hst_edited_path)) hubblePosList = [] hubbleVelList = [] timeHistory = numpy.linspace(etStart, etEnd, numCurvePoints) for timeVal in timeHistory: - stringCurrent = pyswice.et2utc_c(timeVal, 'C', 4, 1024, "Yo") - stateOut = spkRead('HUBBLE SPACE TELESCOPE', stringCurrent, integFrame, zeroBase) + stringCurrent = pyswice.et2utc_c(timeVal, "C", 4, 1024, "Yo") + stateOut = spkRead( + "HUBBLE SPACE TELESCOPE", stringCurrent, integFrame, zeroBase + ) hubblePosList.append(stateOut[0:3].tolist()) hubbleVelList.append(stateOut[3:6].tolist()) @@ -193,7 +213,9 @@ def earthOrbitFit(show_plots): hubbleVelList = numpy.array(hubbleVelList) fitTimes = numpy.linspace(-1, 1, numCurvePoints) - chebCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, hubblePosList, degChebCoeff) + chebCoeff = numpy.polynomial.chebyshev.chebfit( + fitTimes, hubblePosList, degChebCoeff + ) unitTaskName = "unitTask" # arbitrary name (don't change) unitProcessName = "TestProcess" # arbitrary name (don't change) @@ -203,25 +225,30 @@ def earthOrbitFit(show_plots): FSWUnitTestProc = TotalSim.CreateNewProcess(unitProcessName) # create the dynamics task and specify the integration update time - FSWUnitTestProc.addTask(TotalSim.CreateNewTask(unitTaskName, macros.sec2nano(curveDurationSeconds/(numCurvePoints-1)))) + FSWUnitTestProc.addTask( + TotalSim.CreateNewTask( + unitTaskName, macros.sec2nano(curveDurationSeconds / (numCurvePoints - 1)) + ) + ) chebyFitModel = chebyPosEphem.chebyPosEphem() chebyFitModel.ModelTag = "chebyFitModel" TotalSim.AddModelToTask(unitTaskName, chebyFitModel) - totalList = chebCoeff[:,0].tolist() - totalList.extend(chebCoeff[:,1].tolist()) - totalList.extend(chebCoeff[:,2].tolist()) + totalList = chebCoeff[:, 0].tolist() + totalList.extend(chebCoeff[:, 1].tolist()) + totalList.extend(chebCoeff[:, 2].tolist()) chebyFitModel.ephArray[0].posChebyCoeff = totalList - chebyFitModel.ephArray[0].nChebCoeff = degChebCoeff+1 - chebyFitModel.ephArray[0].ephemTimeMid = etStart + curveDurationSeconds/2.0 - chebyFitModel.ephArray[0].ephemTimeRad = curveDurationSeconds/2.0 + chebyFitModel.ephArray[0].nChebCoeff = degChebCoeff + 1 + chebyFitModel.ephArray[0].ephemTimeMid = etStart + curveDurationSeconds / 2.0 + chebyFitModel.ephArray[0].ephemTimeRad = curveDurationSeconds / 2.0 clockCorrData = messaging.TDBVehicleClockCorrelationMsgPayload() clockCorrData.vehicleClockTime = 0.0 - clockCorrData.ephemerisTime = chebyFitModel.ephArray[0].ephemTimeMid - \ - chebyFitModel.ephArray[0].ephemTimeRad + clockCorrData.ephemerisTime = ( + chebyFitModel.ephArray[0].ephemTimeMid - chebyFitModel.ephArray[0].ephemTimeRad + ) clockInMsg = messaging.TDBVehicleClockCorrelationMsg().write(clockCorrData) chebyFitModel.clockCorrInMsg.subscribeTo(clockInMsg) @@ -229,34 +256,44 @@ def earthOrbitFit(show_plots): TotalSim.AddModelToTask(unitTaskName, dataLog) TotalSim.InitializeSimulation() - TotalSim.ConfigureStopTime(int(curveDurationSeconds*1.0E9)) + TotalSim.ConfigureStopTime(int(curveDurationSeconds * 1.0e9)) TotalSim.ExecuteSimulation() posChebData = dataLog.r_BdyZero_N velChebData = dataLog.v_BdyZero_N - maxErrVec = [abs(max(posChebData[:,0] - hubblePosList[:,0])), - abs(max(posChebData[:,1] - hubblePosList[:,1])), - abs(max(posChebData[:,2] - hubblePosList[:,2]))] - maxVelErrVec = [abs(max(velChebData[:,0] - hubbleVelList[:,0])), - abs(max(velChebData[:,1] - hubbleVelList[:,1])), - abs(max(velChebData[:,2] - hubbleVelList[:,2]))] + maxErrVec = [ + abs(max(posChebData[:, 0] - hubblePosList[:, 0])), + abs(max(posChebData[:, 1] - hubblePosList[:, 1])), + abs(max(posChebData[:, 2] - hubblePosList[:, 2])), + ] + maxVelErrVec = [ + abs(max(velChebData[:, 0] - hubbleVelList[:, 0])), + abs(max(velChebData[:, 1] - hubbleVelList[:, 1])), + abs(max(velChebData[:, 2] - hubbleVelList[:, 2])), + ] print("Hubble Orbit Accuracy: " + str(max(maxErrVec))) print("Hubble Velocity Accuracy: " + str(max(maxVelErrVec))) assert (max(maxErrVec)) < orbitPosAccuracy assert (max(maxVelErrVec)) < orbitVelAccuracy plt.figure() - plt.plot(dataLog.times()*1.0E-9, velChebData[:,0], dataLog.times()*1.0E-9, hubbleVelList[:,0]) - - if(show_plots): + plt.plot( + dataLog.times() * 1.0e-9, + velChebData[:, 0], + dataLog.times() * 1.0e-9, + hubbleVelList[:, 0], + ) + + if show_plots: plt.show() - plt.close('all') + plt.close("all") if testFailCount == 0: print("PASSED: " + " Orbit curve fit") # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] + if __name__ == "__main__": test_chebyPosFitAllTest(True) diff --git a/src/fswAlgorithms/transDetermination/oeStateEphem/_UnitTest/test_oeStateEphem.py b/src/fswAlgorithms/transDetermination/oeStateEphem/_UnitTest/test_oeStateEphem.py index 02118958c9..6ec3e53a4b 100644 --- a/src/fswAlgorithms/transDetermination/oeStateEphem/_UnitTest/test_oeStateEphem.py +++ b/src/fswAlgorithms/transDetermination/oeStateEphem/_UnitTest/test_oeStateEphem.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -31,11 +30,13 @@ from Basilisk.utilities import orbitalMotion from Basilisk.utilities import unitTestSupport from Basilisk.utilities.pyswice_spk_utilities import spkRead +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) -splitPath = path.split('fswAlgorithms') +splitPath = path.split("fswAlgorithms") from Basilisk import __path__ + bskPath = __path__[0] orbitPosAccuracy = 10000.0 @@ -44,15 +45,14 @@ unitTestSupport.writeTeXSnippet("toleranceVelValue", str(orbitVelAccuracy), path) -@pytest.mark.parametrize('validChebyCurveTime, anomFlag', [ - (True, 0), - (True, 1), - (True, -1), - (False, -1) -]) +@pytest.mark.parametrize( + "validChebyCurveTime, anomFlag", [(True, 0), (True, 1), (True, -1), (False, -1)] +) def test_chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): """Module Unit Test""" - [testResults, testMessage] = chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag) + [testResults, testMessage] = chebyPosFitAllTest( + show_plots, validChebyCurveTime, anomFlag + ) assert testResults < 1, testMessage @@ -60,31 +60,34 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): # The __tracebackhide__ setting influences pytest showing of tracebacks: # the mrp_steering_tracking() function will not be shown unless the # --fulltrace command line option is specified. - #__tracebackhide__ = True + # __tracebackhide__ = True testFailCount = 0 # zero unit test result counter testMessages = [] # create empty list to store test log messages - numCurvePoints = 4*8640+1 - curveDurationSeconds = 4*86400 + numCurvePoints = 4 * 8640 + 1 + curveDurationSeconds = 4 * 86400 logPeriod = curveDurationSeconds // (numCurvePoints - 1) degChebCoeff = 14 integFrame = "j2000" zeroBase = "Earth" - centralBodyMu = 3.98574405096E14 + centralBodyMu = 3.98574405096e14 dateSpice = "2015 April 10, 00:00:00.0 TDB" - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + pyswice.furnsh_c(str(naif0012_path)) et = pyswice.new_doubleArray(1) pyswice.str2et_c(dateSpice, et) etStart = pyswice.doubleArray_getitem(et, 0) etEnd = etStart + curveDurationSeconds - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.furnsh_c(path + '/TDRSS.bsp') + de430_path = get_path(DataFile.EphemerisData.de430) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(de403masses_path)) + pyswice.furnsh_c(str(pck00010_path)) + pyswice.furnsh_c(path + "/TDRSS.bsp") tdrssPosList = [] tdrssVelList = [] @@ -101,10 +104,10 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): anomCount = 0 for timeVal in timeHistory: - stringCurrent = pyswice.et2utc_c(timeVal, 'C', 4, 1024, "Yo") - stateOut = spkRead('-221', stringCurrent, integFrame, zeroBase) - position = stateOut[0:3]*1000.0 - velocity = stateOut[3:6]*1000.0 + stringCurrent = pyswice.et2utc_c(timeVal, "C", 4, 1024, "Yo") + stateOut = spkRead("-221", stringCurrent, integFrame, zeroBase) + position = stateOut[0:3] * 1000.0 + velocity = stateOut[3:6] * 1000.0 orbEl = orbitalMotion.rv2elem(centralBodyMu, position, velocity) tdrssPosList.append(position) tdrssVelList.append(velocity) @@ -114,12 +117,14 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): OmegaArray.append(orbEl.Omega) omegaArray.append(orbEl.omega) if anomFlag == 1: - currentAnom = orbitalMotion.E2M(orbitalMotion.f2E(orbEl.f, orbEl.e), orbEl.e) + currentAnom = orbitalMotion.E2M( + orbitalMotion.f2E(orbEl.f, orbEl.e), orbEl.e + ) else: currentAnom = orbEl.f if currentAnom < anomPrev: anomCount += 1 - anomArray.append(2*math.pi*anomCount + currentAnom) + anomArray.append(2 * math.pi * anomCount + currentAnom) anomPrev = currentAnom tdrssPosList = numpy.array(tdrssPosList) @@ -129,9 +134,15 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): chebRpCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, rpArray, degChebCoeff) chebEccCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, eccArray, degChebCoeff) chebIncCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, incArray, degChebCoeff) - chebOmegaCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, OmegaArray, degChebCoeff) - chebomegaCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, omegaArray, degChebCoeff) - chebAnomCoeff = numpy.polynomial.chebyshev.chebfit(fitTimes, anomArray, degChebCoeff) + chebOmegaCoeff = numpy.polynomial.chebyshev.chebfit( + fitTimes, OmegaArray, degChebCoeff + ) + chebomegaCoeff = numpy.polynomial.chebyshev.chebfit( + fitTimes, omegaArray, degChebCoeff + ) + chebAnomCoeff = numpy.polynomial.chebyshev.chebfit( + fitTimes, anomArray, degChebCoeff + ) unitTaskName = "unitTask" # arbitrary name (don't change) unitProcessName = "TestProcess" # arbitrary name (don't change) @@ -156,15 +167,17 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): oeStateModel.ephArray[0].anomCoeff = chebAnomCoeff.tolist() oeStateModel.ephArray[0].RAANCoeff = chebOmegaCoeff.tolist() oeStateModel.ephArray[0].nChebCoeff = degChebCoeff + 1 - oeStateModel.ephArray[0].ephemTimeMid = etStart + curveDurationSeconds/2.0 - oeStateModel.ephArray[0].ephemTimeRad = curveDurationSeconds/2.0 + oeStateModel.ephArray[0].ephemTimeMid = etStart + curveDurationSeconds / 2.0 + oeStateModel.ephArray[0].ephemTimeRad = curveDurationSeconds / 2.0 if not (anomFlag == -1): oeStateModel.ephArray[0].anomalyFlag = anomFlag clockCorrData = messaging.TDBVehicleClockCorrelationMsgPayload() clockCorrData.vehicleClockTime = 0.0 - clockCorrData.ephemerisTime = oeStateModel.ephArray[0].ephemTimeMid - oeStateModel.ephArray[0].ephemTimeRad + clockCorrData.ephemerisTime = ( + oeStateModel.ephArray[0].ephemTimeMid - oeStateModel.ephArray[0].ephemTimeRad + ) clockInMsg = messaging.TDBVehicleClockCorrelationMsg().write(clockCorrData) oeStateModel.clockCorrInMsg.subscribeTo(clockInMsg) @@ -176,11 +189,11 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): sim.InitializeSimulation() # increase the run time by one logging period so that the sim time is outside the # valid chebychev curve duration - sim.ConfigureStopTime(int((curveDurationSeconds + logPeriod) * 1.0E9)) + sim.ConfigureStopTime(int((curveDurationSeconds + logPeriod) * 1.0e9)) sim.ExecuteSimulation() else: sim.InitializeSimulation() - sim.ConfigureStopTime(int(curveDurationSeconds*1.0E9)) + sim.ConfigureStopTime(int(curveDurationSeconds * 1.0e9)) sim.ExecuteSimulation() posChebData = dataLog.r_BdyZero_N @@ -192,137 +205,173 @@ def chebyPosFitAllTest(show_plots, validChebyCurveTime, anomFlag): lastPos = posChebData[lastLogidx, 0:] - tdrssPosList[lastLogidx, :] if not numpy.array_equal(secondLastPos, lastPos): testFailCount += 1 - testMessages.append("FAILED: Expected Chebychev position to rail high or low " - + str(secondLastPos) - + " != " - + str(lastPos)) + testMessages.append( + "FAILED: Expected Chebychev position to rail high or low " + + str(secondLastPos) + + " != " + + str(lastPos) + ) secondLastVel = velChebData[lastLogidx + 1, 0:] - tdrssVelList[lastLogidx, :] lastVel = velChebData[lastLogidx, 0:] - tdrssVelList[lastLogidx, :] if not numpy.array_equal(secondLastVel, lastVel): testFailCount += 1 - testMessages.append("FAILED: Expected Chebychev velocity to rail high or low " - + str(secondLastVel) - + " != " - + str(lastVel)) + testMessages.append( + "FAILED: Expected Chebychev velocity to rail high or low " + + str(secondLastVel) + + " != " + + str(lastVel) + ) else: - maxErrVec = [abs(max(posChebData[:, 0] - tdrssPosList[:, 0])), - abs(max(posChebData[:, 1] - tdrssPosList[:, 1])), - abs(max(posChebData[:,2] - tdrssPosList[:, 2]))] - maxVelErrVec = [abs(max(velChebData[:, 0] - tdrssVelList[:, 0])), - abs(max(velChebData[:, 1] - tdrssVelList[:, 1])), - abs(max(velChebData[:, 2] - tdrssVelList[:, 2]))] + maxErrVec = [ + abs(max(posChebData[:, 0] - tdrssPosList[:, 0])), + abs(max(posChebData[:, 1] - tdrssPosList[:, 1])), + abs(max(posChebData[:, 2] - tdrssPosList[:, 2])), + ] + maxVelErrVec = [ + abs(max(velChebData[:, 0] - tdrssVelList[:, 0])), + abs(max(velChebData[:, 1] - tdrssVelList[:, 1])), + abs(max(velChebData[:, 2] - tdrssVelList[:, 2])), + ] if max(maxErrVec) >= orbitPosAccuracy: testFailCount += 1 - testMessages.append("FAILED: maxErrVec >= orbitPosAccuracy, TDRSS Orbit Accuracy: " - + str(max(maxErrVec))) + testMessages.append( + "FAILED: maxErrVec >= orbitPosAccuracy, TDRSS Orbit Accuracy: " + + str(max(maxErrVec)) + ) if max(maxVelErrVec) >= orbitVelAccuracy: testFailCount += 1 - testMessages.append("FAILED: maxVelErrVec >= orbitVelAccuracy, TDRSS Velocity Accuracy: " - + str(max(maxVelErrVec))) + testMessages.append( + "FAILED: maxVelErrVec >= orbitVelAccuracy, TDRSS Velocity Accuracy: " + + str(max(maxVelErrVec)) + ) plt.close("all") # plot the fitted and actual position coordinates plt.figure(1) fig = plt.gcf() ax = fig.gca() - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") for idx in range(0, 3): - plt.plot(dataLog.times()*macros.NANO2HOUR, - posChebData[:, idx]/1000, - color=unitTestSupport.getLineColor(idx, 3), - linewidth=0.5, - label='$r_{fit,' + str(idx) + '}$') - plt.plot(dataLog.times()*macros.NANO2HOUR, - tdrssPosList[:, idx]/1000, - color=unitTestSupport.getLineColor(idx, 3), - linestyle='dashed', linewidth=2, - label='$r_{true,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [h]') - plt.ylabel('Inertial Position [km]') + plt.plot( + dataLog.times() * macros.NANO2HOUR, + posChebData[:, idx] / 1000, + color=unitTestSupport.getLineColor(idx, 3), + linewidth=0.5, + label="$r_{fit," + str(idx) + "}$", + ) + plt.plot( + dataLog.times() * macros.NANO2HOUR, + tdrssPosList[:, idx] / 1000, + color=unitTestSupport.getLineColor(idx, 3), + linestyle="dashed", + linewidth=2, + label="$r_{true," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [h]") + plt.ylabel("Inertial Position [km]") # plot the fitted and actual velocity coordinates plt.figure(2) for idx in range(0, 3): - plt.plot(dataLog.times()*macros.NANO2HOUR, - velChebData[:, idx]/1000, - color=unitTestSupport.getLineColor(idx, 3), - linewidth=0.5, - label='$v_{fit,' + str(idx) + '}$') - plt.plot(dataLog.times()*macros.NANO2HOUR, - tdrssVelList[:, idx]/1000, - color=unitTestSupport.getLineColor(idx, 3), - linestyle='dashed', linewidth=2, - label='$v_{true,' + str(idx) + '}$') - plt.legend(loc='lower right') - plt.xlabel('Time [h]') - plt.ylabel('Velocity [km/s]') + plt.plot( + dataLog.times() * macros.NANO2HOUR, + velChebData[:, idx] / 1000, + color=unitTestSupport.getLineColor(idx, 3), + linewidth=0.5, + label="$v_{fit," + str(idx) + "}$", + ) + plt.plot( + dataLog.times() * macros.NANO2HOUR, + tdrssVelList[:, idx] / 1000, + color=unitTestSupport.getLineColor(idx, 3), + linestyle="dashed", + linewidth=2, + label="$v_{true," + str(idx) + "}$", + ) + plt.legend(loc="lower right") + plt.xlabel("Time [h]") + plt.ylabel("Velocity [km/s]") # plot the difference in position coordinates plt.figure(3) arrayLength = posChebData[:, 0].size - for idx in range(0,3): - plt.plot(dataLog.times() * macros.NANO2HOUR, - posChebData[:, idx] - tdrssPosList[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - linewidth=0.5, - label=r'$\Delta r_{' + str(idx) + '}$') - plt.plot(dataLog.times() * macros.NANO2HOUR, - orbitPosAccuracy*numpy.ones(arrayLength), - color='r', - linewidth=1) - plt.plot(dataLog.times() * macros.NANO2HOUR, - -orbitPosAccuracy * numpy.ones(arrayLength), - color='r', - linewidth=1) - plt.legend(loc='lower right') - plt.xlabel('Time [h]') - plt.ylabel('Position Difference [m]') + for idx in range(0, 3): + plt.plot( + dataLog.times() * macros.NANO2HOUR, + posChebData[:, idx] - tdrssPosList[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + linewidth=0.5, + label=r"$\Delta r_{" + str(idx) + "}$", + ) + plt.plot( + dataLog.times() * macros.NANO2HOUR, + orbitPosAccuracy * numpy.ones(arrayLength), + color="r", + linewidth=1, + ) + plt.plot( + dataLog.times() * macros.NANO2HOUR, + -orbitPosAccuracy * numpy.ones(arrayLength), + color="r", + linewidth=1, + ) + plt.legend(loc="lower right") + plt.xlabel("Time [h]") + plt.ylabel("Position Difference [m]") # plot the difference in velocity coordinates plt.figure(4) arrayLength = velChebData[:, 0].size - for idx in range(0,3): - plt.plot(dataLog.times() * macros.NANO2HOUR, - velChebData[:, idx] - tdrssVelList[:, idx], - color=unitTestSupport.getLineColor(idx, 3), - linewidth=0.5, - label=r'$\Delta v_{' + str(idx) + '}$') - plt.plot(dataLog.times() * macros.NANO2HOUR, - orbitVelAccuracy*numpy.ones(arrayLength), - color='r', - linewidth=1) - plt.plot(dataLog.times() * macros.NANO2HOUR, - -orbitVelAccuracy * numpy.ones(arrayLength), - color='r', - linewidth=1) - plt.legend(loc='lower right') - plt.xlabel('Time [h]') - plt.ylabel('Velocity Difference [m/s]') + for idx in range(0, 3): + plt.plot( + dataLog.times() * macros.NANO2HOUR, + velChebData[:, idx] - tdrssVelList[:, idx], + color=unitTestSupport.getLineColor(idx, 3), + linewidth=0.5, + label=r"$\Delta v_{" + str(idx) + "}$", + ) + plt.plot( + dataLog.times() * macros.NANO2HOUR, + orbitVelAccuracy * numpy.ones(arrayLength), + color="r", + linewidth=1, + ) + plt.plot( + dataLog.times() * macros.NANO2HOUR, + -orbitVelAccuracy * numpy.ones(arrayLength), + color="r", + linewidth=1, + ) + plt.legend(loc="lower right") + plt.xlabel("Time [h]") + plt.ylabel("Velocity Difference [m/s]") if show_plots: plt.show() - plt.close('all') + plt.close("all") snippentName = "passFail" + str(validChebyCurveTime) if testFailCount == 0: - colorText = 'ForestGreen' + colorText = "ForestGreen" print("PASSED: " + oeStateModel.ModelTag) - passedText = r'\textcolor{' + colorText + '}{' + "PASSED" + '}' + passedText = r"\textcolor{" + colorText + "}{" + "PASSED" + "}" else: - colorText = 'Red' + colorText = "Red" print("Failed: " + oeStateModel.ModelTag) - passedText = r'\textcolor{' + colorText + '}{' + "Failed" + '}' + passedText = r"\textcolor{" + colorText + "}{" + "Failed" + "}" unitTestSupport.writeTeXSnippet(snippentName, passedText, path) # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] if __name__ == "__main__": - chebyPosFitAllTest(True, # showPlots - True, # validChebyCurveTime - 1) # anomFlag + chebyPosFitAllTest( + True, # showPlots + True, # validChebyCurveTime + 1, + ) # anomFlag diff --git a/src/simulation/dynamics/GravityGradientEffector/_UnitTest/test_gravityGradient.py b/src/simulation/dynamics/GravityGradientEffector/_UnitTest/test_gravityGradient.py index 3adb1ad2de..e9975b9dff 100644 --- a/src/simulation/dynamics/GravityGradientEffector/_UnitTest/test_gravityGradient.py +++ b/src/simulation/dynamics/GravityGradientEffector/_UnitTest/test_gravityGradient.py @@ -134,8 +134,7 @@ def run(show_plots, cmOffset, planetCase, simTime): # for gravity acceleration calculations venus = gravFactory.createVenus() timeInitString = "2012 MAY 1 00:28:30.0" - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/', - timeInitString, + gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) scSim.AddModelToTask(simTaskName, gravFactory.spiceObject, -1) diff --git a/src/simulation/dynamics/RadiationPressure/_UnitTest/test_radiation_pressure_integrated.py b/src/simulation/dynamics/RadiationPressure/_UnitTest/test_radiation_pressure_integrated.py index 17aa608cad..529f8d21e5 100644 --- a/src/simulation/dynamics/RadiationPressure/_UnitTest/test_radiation_pressure_integrated.py +++ b/src/simulation/dynamics/RadiationPressure/_UnitTest/test_radiation_pressure_integrated.py @@ -79,8 +79,7 @@ def radiationPressureIntegratedTest(show_plots): planet.isCentralBody = True mu = planet.mu gravFactory.createSun() - spice_path = bskPath + '/supportData/EphemerisData/' - gravFactory.createSpiceInterface(spice_path, '2021 MAY 04 07:47:49.965 (UTC)') + gravFactory.createSpiceInterface(time='2021 MAY 04 07:47:49.965 (UTC)') gravFactory.spiceObject.zeroBase = 'Earth' sim.AddModelToTask(simTaskName, gravFactory.spiceObject, -1) srp.sunEphmInMsg.subscribeTo(gravFactory.spiceObject.planetStateOutMsgs[1]) diff --git a/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravityDynEffector.py b/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravityDynEffector.py index b3a0262df5..d0cae2f2fd 100644 --- a/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravityDynEffector.py +++ b/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravityDynEffector.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -26,10 +25,13 @@ path = os.path.dirname(os.path.abspath(filename)) from Basilisk import __path__ + bskPath = __path__[0] from Basilisk.utilities import SimulationBaseClass -from Basilisk.utilities import unitTestSupport # general support file with common unit test functions +from Basilisk.utilities import ( + unitTestSupport, +) # general support file with common unit test functions from Basilisk.utilities import macros from Basilisk.simulation import gravityEffector from Basilisk.simulation import spiceInterface @@ -40,44 +42,51 @@ from Basilisk.architecture import messaging from Basilisk.simulation.gravityEffector import loadGravFromFileToList from Basilisk.architecture.bskLogging import BasiliskError +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + +# script to check spherical harmonics calcs out to 20th degree +# Uses coefficient from Vallado tables D-1 -#script to check spherical harmonics calcs out to 20th degree -#Uses coefficient from Vallado tables D-1 def computeGravityTo20(positionVector): - #This code follows the formulation in Vallado, page 521, second edition and uses data from UTexas CSR for - #gravitation harmonics parameters - #Written 201780807 by Scott Carnahan - #AVS Lab | CU Boulder + # This code follows the formulation in Vallado, page 521, second edition and uses data from UTexas CSR for + # gravitation harmonics parameters + # Written 201780807 by Scott Carnahan + # AVS Lab | CU Boulder - #INPUTS - #positionVector - [x,y,z] coordinates list of spacecraft in [m] in earth body frame so that lat, long can be calculated + # INPUTS + # positionVector - [x,y,z] coordinates list of spacecraft in [m] in earth body frame so that lat, long can be calculated def legendres(degree, alpha): - P = np.zeros((degree+1,degree+1)) - P[0,0] = 1 - P[1,0] = alpha - cosPhi = np.sqrt(1-alpha**2) - P[1,1] = cosPhi - - for l in range(2,degree+1): - for m in range(0,l+1): + P = np.zeros((degree + 1, degree + 1)) + P[0, 0] = 1 + P[1, 0] = alpha + cosPhi = np.sqrt(1 - alpha**2) + P[1, 1] = cosPhi + + for l in range(2, degree + 1): + for m in range(0, l + 1): if m == 0 and l >= 2: - P[l,m] = ((2*l-1)*alpha*P[l-1,0]-(l-1)*P[l-2,0]) / l + P[l, m] = ( + (2 * l - 1) * alpha * P[l - 1, 0] - (l - 1) * P[l - 2, 0] + ) / l elif m != 0 and m < l: - P[l, m] = (P[l-2, m]+(2*l-1)*cosPhi*P[l-1,m-1]) + P[l, m] = P[l - 2, m] + (2 * l - 1) * cosPhi * P[l - 1, m - 1] elif m == l and l != 0: - P[l,m] = (2*l-1)*cosPhi*P[l-1,m-1] + P[l, m] = (2 * l - 1) * cosPhi * P[l - 1, m - 1] else: - print(l,", ", m) + print(l, ", ", m) return P maxDegree = 20 - cList = np.zeros(maxDegree+2) - sList = np.zeros(maxDegree+2) - muEarth = 0. - radEarth = 0. - [cList, sList, muEarth, radEarth] = loadGravFromFileToList(path + '/GGM03S.txt', maxDegree+2) + cList = np.zeros(maxDegree + 2) + sList = np.zeros(maxDegree + 2) + muEarth = 0.0 + radEarth = 0.0 + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + [cList, sList, muEarth, radEarth] = loadGravFromFileToList( + str(ggm03s_path), maxDegree + 2 + ) r = np.linalg.norm(positionVector) rHat = positionVector / r @@ -89,56 +98,77 @@ def legendres(degree, alpha): rK = positionVector[2] rIJ = np.sqrt(rI**2 + rJ**2) - if rIJ != 0.: - phi = math.atan(rK / rIJ) #latitude in radians + if rIJ != 0.0: + phi = math.atan(rK / rIJ) # latitude in radians else: - phi = math.copysign(np.pi/2., rK) - if rI != 0.: - lambdaSat = math.atan(rJ / rI) #longitude in radians + phi = math.copysign(np.pi / 2.0, rK) + if rI != 0.0: + lambdaSat = math.atan(rJ / rI) # longitude in radians else: - lambdaSat = math.copysign(np.pi/2., rJ) + lambdaSat = math.copysign(np.pi / 2.0, rJ) - P = legendres(maxDegree+1,np.sin(phi)) + P = legendres(maxDegree + 1, np.sin(phi)) - dUdr = 0. - dUdphi = 0. - dUdlambda = 0. + dUdr = 0.0 + dUdphi = 0.0 + dUdlambda = 0.0 - for l in range(0, maxDegree+1): - for m in range(0,l+1): + for l in range(0, maxDegree + 1): + for m in range(0, l + 1): if m == 0: k = 1 else: k = 2 - num = math.factorial(l+m) - den = math.factorial(l-m)*k*(2*l+1) - PI = np.sqrt(float(num)/float(den)) + num = math.factorial(l + m) + den = math.factorial(l - m) * k * (2 * l + 1) + PI = np.sqrt(float(num) / float(den)) cList[l][m] = cList[l][m] / PI sList[l][m] = sList[l][m] / PI - for l in range(2,maxDegree+1): #can only do for max degree minus 1 - for m in range(0,l+1): - dUdr = dUdr + (((radEarth/r)**l)*(l+1)*P[l,m]) * (cList[l][m]*np.cos(m*lambdaSat)+sList[l][m]*np.sin(m*lambdaSat)) - dUdphi = dUdphi + (((radEarth/r)**l)*P[l,m+1] - m*np.tan(phi)*P[l,m]) * (cList[l][m]*np.cos(m*lambdaSat) + sList[l][m]*np.sin(m*lambdaSat)) - dUdlambda = dUdlambda + (((radEarth/r)**l)*m*P[l,m]) * (sList[l][m]*np.cos(m*lambdaSat) - cList[l][m]*np.sin(m*lambdaSat)) + for l in range(2, maxDegree + 1): # can only do for max degree minus 1 + for m in range(0, l + 1): + dUdr = dUdr + (((radEarth / r) ** l) * (l + 1) * P[l, m]) * ( + cList[l][m] * np.cos(m * lambdaSat) + + sList[l][m] * np.sin(m * lambdaSat) + ) + dUdphi = dUdphi + ( + ((radEarth / r) ** l) * P[l, m + 1] - m * np.tan(phi) * P[l, m] + ) * ( + cList[l][m] * np.cos(m * lambdaSat) + + sList[l][m] * np.sin(m * lambdaSat) + ) + dUdlambda = dUdlambda + (((radEarth / r) ** l) * m * P[l, m]) * ( + sList[l][m] * np.cos(m * lambdaSat) + - cList[l][m] * np.sin(m * lambdaSat) + ) dUdr = -muEarth * dUdr / r**2 dUdphi = muEarth * dUdphi / r dUdlambda = muEarth * dUdlambda / r - - if rI != 0. and rJ != 0.: - accelerationI = (dUdr/r - rK*dUdphi/(r**2)/((rI**2+rJ**2)**0.5))*rI - (dUdlambda/(rI**2+rJ**2))*rJ + grav0[0] - accelerationJ = (dUdr/r - rK*dUdphi/(r**2)/((rI**2+rJ**2)**0.5))*rJ + (dUdlambda/(rI**2+rJ**2))*rI + grav0[1] + if rI != 0.0 and rJ != 0.0: + accelerationI = ( + (dUdr / r - rK * dUdphi / (r**2) / ((rI**2 + rJ**2) ** 0.5)) * rI + - (dUdlambda / (rI**2 + rJ**2)) * rJ + + grav0[0] + ) + accelerationJ = ( + (dUdr / r - rK * dUdphi / (r**2) / ((rI**2 + rJ**2) ** 0.5)) * rJ + + (dUdlambda / (rI**2 + rJ**2)) * rI + + grav0[1] + ) else: - accelerationI = dUdr/r + grav0[0] - accelerationJ = dUdr/r + grav0[1] - accelerationK = (dUdr/r)*rK + (((rI**2+rJ**2)**0.5)*dUdphi/(r**2)) + grav0[2] + accelerationI = dUdr / r + grav0[0] + accelerationJ = dUdr / r + grav0[1] + accelerationK = ( + (dUdr / r) * rK + (((rI**2 + rJ**2) ** 0.5) * dUdphi / (r**2)) + grav0[2] + ) accelerationVector = [accelerationI, accelerationJ, accelerationK] return accelerationVector + # uncomment this line is this test is to be skipped in the global unit test run, adjust message as needed # @pytest.mark.skipif(conditionstring) # uncomment this line if this test has an expected failure, adjust message as needed @@ -155,6 +185,7 @@ def test_gravityEffectorAllTest(show_plots): [testResults, testMessage] = multiBodyGravity(show_plots) assert testResults < 1, testMessage + def independentSphericalHarmonics(show_plots): testCase = "independentCheck" # The __tracebackhide__ setting influences pytest showing of tracebacks: @@ -167,52 +198,66 @@ def independentSphericalHarmonics(show_plots): spherHarm = gravityEffector.SphericalHarmonics() - gravityEffector.loadGravFromFile(path + '/GGM03S.txt', spherHarm, 20) - gravCheck = computeGravityTo20([15000., 10000., 6378.1363E3]) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + gravityEffector.loadGravFromFile(str(ggm03s_path), spherHarm, 20) + gravCheck = computeGravityTo20([15000.0, 10000.0, 6378.1363e3]) spherHarm.initializeParameters() - gravOut = spherHarm.computeField([[15000.0], [10000.0], [(6378.1363) * 1.0E3]], 20, True) + gravOut = spherHarm.computeField( + [[15000.0], [10000.0], [(6378.1363) * 1.0e3]], 20, True + ) gravOutMag = np.linalg.norm(gravOut) gravCheckMag = np.linalg.norm(gravCheck) accuracy = 1e-12 - relative = (gravCheckMag-gravOutMag)/gravCheckMag + relative = (gravCheckMag - gravOutMag) / gravCheckMag if abs(relative) > accuracy: testFailCount += 1 testMessages.append("Failed independent spherical harmonics check") - snippetName = testCase + 'Accuracy' - snippetContent = '{:1.1e}'.format(accuracy) # write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, - path) # write formatted LATEX string to file to be used by auto-documentation. + snippetName = testCase + "Accuracy" + snippetContent = "{:1.1e}".format( + accuracy + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. if testFailCount == 0: - passFailText = 'PASSED' + passFailText = "PASSED" print("PASSED: " + testCase) - colorText = 'ForestGreen' # color to write auto-documented "PASSED" message in in LATEX. - snippetName = testCase + 'FailMsg' + colorText = "ForestGreen" # color to write auto-documented "PASSED" message in in LATEX. + snippetName = testCase + "FailMsg" snippetContent = "" - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, - path) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. else: - passFailText = 'FAILED' - colorText = 'Red' # color to write auto-documented "FAILED" message in in LATEX - snippetName = testCase + 'FailMsg' + passFailText = "FAILED" + colorText = "Red" # color to write auto-documented "FAILED" message in in LATEX + snippetName = testCase + "FailMsg" snippetContent = passFailText for message in testMessages: snippetContent += ". " + message snippetContent += "." - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, - path) # write formatted LATEX string to file to be used by auto-documentation. - snippetName = testCase + 'PassFail' # name of file to be written for auto-documentation which specifies if this test was passed or failed. - snippetContent = r'\textcolor{' + colorText + '}{' + passFailText + '}' # write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, - path) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + snippetName = ( + testCase + "PassFail" + ) # name of file to be written for auto-documentation which specifies if this test was passed or failed. + snippetContent = ( + r"\textcolor{" + colorText + "}{" + passFailText + "}" + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] + def sphericalHarmonics(show_plots): - testCase = 'sphericalHarmonics' + testCase = "sphericalHarmonics" # The __tracebackhide__ setting influences pytest showing of tracebacks: # the mrp_steering_tracking() function will not be shown unless the # --fulltrace command line option is specified. @@ -233,14 +278,14 @@ def sphericalHarmonics(show_plots): if spherHarm.cBar[i][j] != testHarm[i][j]: vecCheckSuccess = False - - if(vecCheckSuccess != True): + if vecCheckSuccess != True: testFailCount += 1 testMessages.append("2D vector not input appropriately to spherical harmonics") - gravityEffector.loadGravFromFile(path + '/GGM03S.txt', spherHarm, 20) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + gravityEffector.loadGravFromFile(str(ggm03s_path), spherHarm, 20) spherHarm.initializeParameters() - gravOut = spherHarm.computeField([[0.0], [0.0], [(6378.1363)*1.0E3]], 20, True) + gravOut = spherHarm.computeField([[0.0], [0.0], [(6378.1363) * 1.0e3]], 20, True) gravMag = np.linalg.norm(np.array(gravOut)) accuracy = 0.1 @@ -248,43 +293,58 @@ def sphericalHarmonics(show_plots): if gravMag > (gravExpected + accuracy) or gravMag < (gravExpected - accuracy): testFailCount += 1 testMessages.append("Gravity magnitude not within allowable tolerance") - snippetName = testCase + 'Accuracy' - snippetContent = '{:1.1e}'.format(accuracy) # write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) #write formatted LATEX string to file to be used by auto-documentation. + snippetName = testCase + "Accuracy" + snippetContent = "{:1.1e}".format( + accuracy + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. try: - spherHarm.computeField([[0.0], [0.0], [(6378.1363)*1.0E3]], 100, True) + spherHarm.computeField([[0.0], [0.0], [(6378.1363) * 1.0e3]], 100, True) testFailCount += 1 testMessages.append("Gravity ceiling not enforced correctly") except BasiliskError: - pass # Great! We threw an error + pass # Great! We threw an error if testFailCount == 0: - passFailText = 'PASSED' + passFailText = "PASSED" print("PASSED: " + " Spherical Harmonics") - colorText = 'ForestGreen' # color to write auto-documented "PASSED" message in in LATEX. - snippetName = testCase + 'FailMsg' + colorText = "ForestGreen" # color to write auto-documented "PASSED" message in in LATEX. + snippetName = testCase + "FailMsg" snippetContent = "" - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. else: - passFailText = 'FAILED' - colorText = 'Red' # color to write auto-documented "FAILED" message in in LATEX - snippetName = testCase + 'FailMsg' + passFailText = "FAILED" + colorText = "Red" # color to write auto-documented "FAILED" message in in LATEX + snippetName = testCase + "FailMsg" snippetContent = passFailText for message in testMessages: snippetContent += ". " + message snippetContent += "." - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) # write formatted LATEX string to file to be used by auto-documentation. - snippetName = testCase + 'PassFail' # name of file to be written for auto-documentation which specifies if this test was passed or failed. - snippetContent = r'\textcolor{' + colorText + '}{' + passFailText + '}' #write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) #write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + snippetName = ( + testCase + "PassFail" + ) # name of file to be written for auto-documentation which specifies if this test was passed or failed. + snippetContent = ( + r"\textcolor{" + colorText + "}{" + passFailText + "}" + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] + def singleGravityBody(show_plots): - testCase = 'singleBody' + testCase = "singleBody" # The __tracebackhide__ setting influences pytest showing of tracebacks: # the mrp_steering_tracking() function will not be shown unless the # --fulltrace command line option is specified. @@ -309,8 +369,10 @@ def singleGravityBody(show_plots): SpiceObject = spiceInterface.SpiceInterface() SpiceObject.ModelTag = "SpiceInterfaceData" - SpiceObject.SPICEDataPath = bskPath + '/supportData/EphemerisData/' - SpiceObject.addPlanetNames(spiceInterface.StringVector(["earth", "mars barycenter", "sun"])) + SpiceObject.SPICEDataPath = bskPath + "/supportData/EphemerisData/" + SpiceObject.addPlanetNames( + spiceInterface.StringVector(["earth", "mars barycenter", "sun"]) + ) SpiceObject.UTCCalInit = DateSpice TotalSim.AddModelToTask(unitTaskName, SpiceObject) SpiceObject.UTCCalInit = "1994 JAN 26 00:02:00.184" @@ -318,7 +380,8 @@ def singleGravityBody(show_plots): gravBody1 = gravityEffector.GravBodyData() gravBody1.planetName = "earth_planet_data" gravBody1.isCentralBody = False - gravBody1.useSphericalHarmonicsGravityModel(path + '/GGM03S.txt', 60) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + gravBody1.useSphericalHarmonicsGravityModel(str(ggm03s_path), 60) gravBody1.planetBodyInMsg.subscribeTo(SpiceObject.planetStateOutMsgs[0]) # Use the python spice utility to load in spacecraft SPICE ephemeris data @@ -326,11 +389,16 @@ def singleGravityBody(show_plots): # separate from the earlier SPICE setup that was loaded to BSK. This is why # all required SPICE libraries must be included when setting up and loading # SPICE kernals in Python. - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.furnsh_c(path + '/hst_edited.bsp') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(naif0012_path)) + pyswice.furnsh_c(str(de403masses_path)) + pyswice.furnsh_c(str(pck00010_path)) + pyswice.furnsh_c(str(hst_edited_path)) SpiceObject.UTCCalInit = "2012 MAY 1 00:02:00.184" stringCurrent = SpiceObject.UTCCalInit @@ -347,71 +415,98 @@ def singleGravityBody(show_plots): gravBody1.registerProperties(newManager) SpiceObject.UpdateState(0) - for i in range(2*3600): - stateOut = spkRead('HUBBLE SPACE TELESCOPE', stringCurrent, 'J2000', 'EARTH') - etPrev =etCurr - 2.0 - stringPrev = pyswice.et2utc_c(etPrev, 'C', 4, 1024, "Yo") - statePrev = spkRead('HUBBLE SPACE TELESCOPE', stringPrev, 'J2000', 'EARTH') - etNext =etCurr + 2.0 - stringNext = pyswice.et2utc_c(etNext, 'C', 4, 1024, "Yo") - stateNext = spkRead('HUBBLE SPACE TELESCOPE', stringNext, 'J2000', 'EARTH') - gravVec = (stateNext[3:6] - statePrev[3:6])/(etNext - etPrev) + for i in range(2 * 3600): + stateOut = spkRead("HUBBLE SPACE TELESCOPE", stringCurrent, "J2000", "EARTH") + etPrev = etCurr - 2.0 + stringPrev = pyswice.et2utc_c(etPrev, "C", 4, 1024, "Yo") + statePrev = spkRead("HUBBLE SPACE TELESCOPE", stringPrev, "J2000", "EARTH") + etNext = etCurr + 2.0 + stringNext = pyswice.et2utc_c(etNext, "C", 4, 1024, "Yo") + stateNext = spkRead("HUBBLE SPACE TELESCOPE", stringNext, "J2000", "EARTH") + gravVec = (stateNext[3:6] - statePrev[3:6]) / (etNext - etPrev) normVec.append(np.linalg.norm(stateOut[0:3])) - stateOut*=1000.0 - SpiceObject.J2000Current = etCurr;SpiceObject.UpdateState(0) + stateOut *= 1000.0 + SpiceObject.J2000Current = etCurr + SpiceObject.UpdateState(0) gravBody1.loadEphemeris() - gravOut = gravBody1.computeGravityInertial(stateOut[0:3].reshape(3,1).tolist(), 0) - gravErrNorm.append(np.linalg.norm(gravVec*1000.0 - np.array(gravOut).reshape(3))/ - np.linalg.norm(gravVec*1000.0)) + gravOut = gravBody1.computeGravityInertial( + stateOut[0:3].reshape(3, 1).tolist(), 0 + ) + gravErrNorm.append( + np.linalg.norm(gravVec * 1000.0 - np.array(gravOut).reshape(3)) + / np.linalg.norm(gravVec * 1000.0) + ) pyswice.str2et_c(stringCurrent, et) etCurr = pyswice.doubleArray_getitem(et, 0) - etCurr += dt; - stringCurrent = pyswice.et2utc_c(etCurr, 'C', 4, 1024, "Yo") + etCurr += dt + stringCurrent = pyswice.et2utc_c(etCurr, "C", 4, 1024, "Yo") accuracy = 1.0e-4 for gravErr in gravErrNorm: if gravErr > accuracy: testFailCount += 1 - testMessages.append("Gravity numerical error too high for kernel comparison") + testMessages.append( + "Gravity numerical error too high for kernel comparison" + ) break - snippetName = testCase + 'Accuracy' - snippetContent = '{:1.1e}'.format(accuracy) # write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) #write formatted LATEX string to file to be used by auto-documentation. - - pyswice.unload_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.unload_c(path + '/hst_edited.bsp') - + snippetName = testCase + "Accuracy" + snippetContent = "{:1.1e}".format( + accuracy + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + pyswice.unload_c(str(de430_path)) + pyswice.unload_c(str(naif0012_path)) + pyswice.unload_c(str(de403masses_path)) + pyswice.unload_c(str(pck00010_path)) + pyswice.unload_c(str(hst_edited_path)) if testFailCount == 0: - passFailText = 'PASSED' + passFailText = "PASSED" print("PASSED: " + "Single-body with Spherical Harmonics") - colorText = 'ForestGreen' # color to write auto-documented "PASSED" message in in LATEX - snippetName = testCase + 'FailMsg' + colorText = ( + "ForestGreen" # color to write auto-documented "PASSED" message in in LATEX + ) + snippetName = testCase + "FailMsg" snippetContent = "" - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. else: - passFailText = 'FAILED' - colorText = 'Red' # color to write auto-documented "FAILED" message in in LATEX - snippetName = testCase + 'FailMsg' + passFailText = "FAILED" + colorText = "Red" # color to write auto-documented "FAILED" message in in LATEX + snippetName = testCase + "FailMsg" snippetContent = passFailText for message in testMessages: snippetContent += ". " + message snippetContent += "." - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) # write formatted LATEX string to file to be used by auto-documentation. - snippetName = testCase + 'PassFail' # name of file to be written for auto-documentation which specifies if this test was passed or failed. - snippetContent = r'\textcolor{' + colorText + '}{' + passFailText + '}' #write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) #write formatted LATEX string to file to be used by auto-documentation. - + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + snippetName = ( + testCase + "PassFail" + ) # name of file to be written for auto-documentation which specifies if this test was passed or failed. + snippetContent = ( + r"\textcolor{" + colorText + "}{" + passFailText + "}" + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] + def register(manager): """ @@ -423,7 +518,7 @@ def register(manager): positionName = "hubPosition" stateDim = [3, 1] posState = manager.registerState(stateDim[0], stateDim[1], positionName) - posVelSig = [[0.], [0.], [0.]] + posVelSig = [[0.0], [0.0], [0.0]] posState.setState(posVelSig) velocityName = "hubVelocity" stateDim = [3, 1] @@ -439,8 +534,9 @@ def register(manager): return + def multiBodyGravity(show_plots): - testCase = 'multiBody' #for AutoTeX stuff + testCase = "multiBody" # for AutoTeX stuff # The __tracebackhide__ setting influences pytest showing of tracebacks: # the mrp_steering_tracking() function will not be shown unless the # --fulltrace command line option is specified. @@ -459,54 +555,59 @@ def multiBodyGravity(show_plots): # DynUnitTestProc = multiSim.CreateNewProcess(unitProcessName) # # create the dynamics task and specify the integration update time - DynUnitTestProc.addTask(multiSim.CreateNewTask(unitTaskName, macros.sec2nano(1000.0))) - - #Create dynParamManager to feed fake spacecraft data to so that the gravityEffector can access it. - #This places the spacecraft at the coordinate frame origin so that planets can be placed around it. - #velocity and attitude are just set to zero. - #center of mass and time are set to zero. + DynUnitTestProc.addTask( + multiSim.CreateNewTask(unitTaskName, macros.sec2nano(1000.0)) + ) + + # Create dynParamManager to feed fake spacecraft data to so that the gravityEffector can access it. + # This places the spacecraft at the coordinate frame origin so that planets can be placed around it. + # velocity and attitude are just set to zero. + # center of mass and time are set to zero. newManager = stateArchitecture.DynParamManager() register(newManager) - - #Create a message struct to place gravBody1 where it is wanted + # Create a message struct to place gravBody1 where it is wanted localPlanetEditor = messaging.SpicePlanetStateMsgPayload() - localPlanetEditor.PositionVector = [om.AU/10., 0., 0.] - localPlanetEditor.VelocityVector = [0., 0., 0.] + localPlanetEditor.PositionVector = [om.AU / 10.0, 0.0, 0.0] + localPlanetEditor.VelocityVector = [0.0, 0.0, 0.0] localPlanetEditor.J20002Pfix = np.identity(3) - #Grav Body 1 is twice the size of the other two + # Grav Body 1 is twice the size of the other two gravBody1 = gravityEffector.GravBodyData() gravBody1.planetName = "gravBody1_planet_data" - gravBody1.mu = 1000000. - gravBody1.radEquator = 6500. + gravBody1.mu = 1000000.0 + gravBody1.radEquator = 6500.0 gravBody1.isCentralBody = False gravBody1.localPlanet = localPlanetEditor - #This is the gravityEffector which will actually compute the gravitational acceleration + # This is the gravityEffector which will actually compute the gravitational acceleration allGrav = gravityEffector.GravityEffector() allGrav.gravBodies = gravityEffector.GravBodyVector([gravBody1]) allGrav.linkInStates(newManager) allGrav.registerProperties(newManager) allGrav.Reset(0) multiSim.AddModelToTask(unitTaskName, allGrav) - posVelSig = [[0.], [0.], [0.]] - allGrav.computeGravityField(posVelSig, posVelSig) #compute acceleration only considering the first body. - step1 = newManager.getPropertyReference(allGrav.vehicleGravityPropName) #retrieve total gravitational acceleration in inertial frame - - #Create a message struct to place gravBody2&3 where they are wanted. - localPlanetEditor.PositionVector = [-om.AU/10., 0., 0.] - localPlanetEditor.VelocityVector = [0., 0., 0.] + posVelSig = [[0.0], [0.0], [0.0]] + allGrav.computeGravityField( + posVelSig, posVelSig + ) # compute acceleration only considering the first body. + step1 = newManager.getPropertyReference( + allGrav.vehicleGravityPropName + ) # retrieve total gravitational acceleration in inertial frame + + # Create a message struct to place gravBody2&3 where they are wanted. + localPlanetEditor.PositionVector = [-om.AU / 10.0, 0.0, 0.0] + localPlanetEditor.VelocityVector = [0.0, 0.0, 0.0] - #grav Body 2 and 3 are coincident with each other, half the mass of gravBody1 and are in the opposite direction of gravBody1 + # grav Body 2 and 3 are coincident with each other, half the mass of gravBody1 and are in the opposite direction of gravBody1 gravBody2 = gravityEffector.GravBodyData() gravBody2.planetName = "gravBody2_planet_data" - gravBody2.mu = gravBody1.mu/2. - gravBody2.radEquator = 6500. + gravBody2.mu = gravBody1.mu / 2.0 + gravBody2.radEquator = 6500.0 gravBody2.isCentralBody = False gravBody2.localPlanet = localPlanetEditor - #This is the gravityEffector which will actually compute the gravitational acceleration + # This is the gravityEffector which will actually compute the gravitational acceleration newManager = stateArchitecture.DynParamManager() register(newManager) allGrav2 = gravityEffector.GravityEffector() @@ -515,66 +616,102 @@ def multiBodyGravity(show_plots): allGrav2.registerProperties(newManager) allGrav2.Reset(0) multiSim.AddModelToTask(unitTaskName, allGrav2) - allGrav2.computeGravityField(posVelSig, posVelSig) #compute acceleration considering the first and second bodies. - step2 = newManager.getPropertyReference(allGrav2.vehicleGravityPropName) #retrieve total gravitational acceleration in inertial frame + allGrav2.computeGravityField( + posVelSig, posVelSig + ) # compute acceleration considering the first and second bodies. + step2 = newManager.getPropertyReference( + allGrav2.vehicleGravityPropName + ) # retrieve total gravitational acceleration in inertial frame # grav Body 2 and 3 are coincident with each other, half the mass of gravBody1 and are in the opposite direction of gravBody1 gravBody3 = gravityEffector.GravBodyData() gravBody3.planetName = "gravBody3_planet_data" gravBody3.mu = gravBody2.mu - gravBody3.radEquator = 6500. + gravBody3.radEquator = 6500.0 gravBody3.isCentralBody = False gravBody3.localPlanet = localPlanetEditor - #This is the gravityEffector which will actually compute the gravitational acceleration + # This is the gravityEffector which will actually compute the gravitational acceleration newManager = stateArchitecture.DynParamManager() register(newManager) allGrav3 = gravityEffector.GravityEffector() - allGrav3.gravBodies = gravityEffector.GravBodyVector([gravBody1, gravBody2, gravBody3]) + allGrav3.gravBodies = gravityEffector.GravBodyVector( + [gravBody1, gravBody2, gravBody3] + ) allGrav3.linkInStates(newManager) allGrav3.registerProperties(newManager) allGrav3.Reset(0) multiSim.AddModelToTask(unitTaskName, allGrav3) - allGrav3.computeGravityField(posVelSig, posVelSig) #comput acceleration considering all three bodies - step3 = newManager.getPropertyReference(allGrav3.vehicleGravityPropName) #retrieve total gravitational acceleration in inertial frame - - step3 = [0., step3[0][0], step3[1][0], step3[2][0]] #add a first (time) column to use isArrayZero - - #Test results for accuracy + allGrav3.computeGravityField( + posVelSig, posVelSig + ) # comput acceleration considering all three bodies + step3 = newManager.getPropertyReference( + allGrav3.vehicleGravityPropName + ) # retrieve total gravitational acceleration in inertial frame + + step3 = [ + 0.0, + step3[0][0], + step3[1][0], + step3[2][0], + ] # add a first (time) column to use isArrayZero + + # Test results for accuracy accuracy = 1e-12 - snippetName = testCase + 'Accuracy' - snippetContent = '{:1.1e}'.format(accuracy) # write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) #write formatted LATEX string to file to be used by auto-documentation. - if not unitTestSupport.isDoubleEqualRelative(step2[0][0]/step1[0][0], 0.5, accuracy): #if the second grav body doesn't cancel exactly half of the first body's acceleration. + snippetName = testCase + "Accuracy" + snippetContent = "{:1.1e}".format( + accuracy + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + if not unitTestSupport.isDoubleEqualRelative( + step2[0][0] / step1[0][0], 0.5, accuracy + ): # if the second grav body doesn't cancel exactly half of the first body's acceleration. testFailCount += 1 passFailText = "Step 2 was not half of step 1" testMessages.append(passFailText) - elif not unitTestSupport.isArrayZero(step3, 3, accuracy): #if the net acceleration is not now 0. + elif not unitTestSupport.isArrayZero( + step3, 3, accuracy + ): # if the net acceleration is not now 0. testFailCount += 1 passFailText = "Step 3 did not cause gravity to return to 0" testMessages.append(passFailText) - #Record test results to LaTeX + # Record test results to LaTeX if testFailCount == 0: - passFailText = 'PASSED' + passFailText = "PASSED" print("PASSED: " + " Multi-Body") - colorText = 'ForestGreen' # color to write auto-documented "PASSED" message in in LATEX - snippetName = testCase + 'FailMsg' + colorText = ( + "ForestGreen" # color to write auto-documented "PASSED" message in in LATEX + ) + snippetName = testCase + "FailMsg" snippetContent = "" - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. else: - passFailText = 'FAILED' - colorText = 'Red' # color to write auto-documented "FAILED" message in in LATEX - snippetName = testCase + 'FailMsg' + passFailText = "FAILED" + colorText = "Red" # color to write auto-documented "FAILED" message in in LATEX + snippetName = testCase + "FailMsg" snippetContent = passFailText for message in testMessages: snippetContent += ". " + message snippetContent += "." - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) # write formatted LATEX string to file to be used by auto-documentation. - snippetName = testCase + 'PassFail' # name of file to be written for auto-documentation which specifies if this test was passed or failed. - snippetContent = r'\textcolor{' + colorText + '}{' + passFailText + '}' #write formatted LATEX string to file to be used by auto-documentation. - unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path) #write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + snippetName = ( + testCase + "PassFail" + ) # name of file to be written for auto-documentation which specifies if this test was passed or failed. + snippetContent = ( + r"\textcolor{" + colorText + "}{" + passFailText + "}" + ) # write formatted LATEX string to file to be used by auto-documentation. + unitTestSupport.writeTeXSnippet( + snippetName, snippetContent, path + ) # write formatted LATEX string to file to be used by auto-documentation. + + return [testFailCount, "".join(testMessages)] - return [testFailCount, ''.join(testMessages)] if __name__ == "__main__": # test_gravityEffectorAllTest(False) diff --git a/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravitySpacecraft.py b/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravitySpacecraft.py index 9f43b12f0c..c0ba09b8eb 100644 --- a/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravitySpacecraft.py +++ b/src/simulation/dynamics/gravityEffector/_UnitTest/test_gravitySpacecraft.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -25,6 +24,7 @@ filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) from Basilisk import __path__ + bskPath = __path__[0] from Basilisk.utilities import macros @@ -37,17 +37,18 @@ from Basilisk.simulation import planetEphemeris from Basilisk.simulation import spacecraft from Basilisk.utilities import simIncludeGravBody +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile import pytest + # uncomment this line is this test is to be skipped in the global unit test run, adjust message as needed # @pytest.mark.skipif(conditionstring) # uncomment this line if this test has an expected failure, adjust message as needed # @pytest.mark.xfail() # need to update how the RW states are defined # provide a unique test method name, starting with test_ -@pytest.mark.parametrize("function", ["singleGravityBody" - , "multiBodyGravity" - , "polyGravityBody" - ]) +@pytest.mark.parametrize( + "function", ["singleGravityBody", "multiBodyGravity", "polyGravityBody"] +) def test_gravityEffectorAllTest(show_plots, function): """Module Unit Test""" testFunction = globals().get(function) @@ -81,21 +82,25 @@ def singleGravityBody(show_plots): DynUnitTestProc = unitTestSim.CreateNewProcess(unitProcessName) # create the dynamics task and specify the integration update time - DynUnitTestProc.addTask(unitTestSim.CreateNewTask(unitTaskName, macros.sec2nano(10.0))) - + DynUnitTestProc.addTask( + unitTestSim.CreateNewTask(unitTaskName, macros.sec2nano(10.0)) + ) # setup Gravity Bodies gravFactory = simIncludeGravBody.gravBodyFactory() - gravBodies = gravFactory.createBodies(['earth', 'sun', 'moon', 'jupiter barycenter']) - gravBodies['earth'].isCentralBody = True - gravBodies['earth'].useSphericalHarmonicsGravityModel(path + '/../_UnitTest/GGM03S.txt' - , 40 - ) + gravBodies = gravFactory.createBodies( + ["earth", "sun", "moon", "jupiter barycenter"] + ) + gravBodies["earth"].isCentralBody = True + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + gravBodies["earth"].useSphericalHarmonicsGravityModel(str(ggm03s_path), 40) stringCurrent = "2016 MAY 1 00:32:30.0" - gravFactory.createSpiceInterface(bskPath +'/supportData/EphemerisData/', stringCurrent) - gravFactory.spiceObject.zeroBase = 'Earth' + gravFactory.createSpiceInterface(time=stringCurrent) + gravFactory.spiceObject.zeroBase = "Earth" - scObject.gravField.gravBodies = spacecraft.GravBodyVector(list(gravFactory.gravBodies.values())) + scObject.gravField.gravBodies = spacecraft.GravBodyVector( + list(gravFactory.gravBodies.values()) + ) unitTestSim.AddModelToTask(unitTaskName, gravFactory.spiceObject, 10) @@ -104,23 +109,28 @@ def singleGravityBody(show_plots): # separate from the earlier SPICE setup that was loaded to BSK. This is why # all required SPICE libraries must be included when setting up and loading # SPICE kernels in Python. - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.furnsh_c(path + '/../_UnitTest/hst_edited.bsp') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(naif0012_path)) + pyswice.furnsh_c(str(de403masses_path)) + pyswice.furnsh_c(str(pck00010_path)) + pyswice.furnsh_c(str(hst_edited_path)) unitTestSim.AddModelToTask(unitTaskName, scObject, 9) - stateOut = spkRead('HUBBLE SPACE TELESCOPE', stringCurrent, 'J2000', 'EARTH') + stateOut = spkRead("HUBBLE SPACE TELESCOPE", stringCurrent, "J2000", "EARTH") scObject.hub.mHub = 100 scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] scObject.hub.IHubPntBc_B = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] - scObject.hub.r_CN_NInit = (1000.0*stateOut[0:3].reshape(3,1)).tolist() - velStart = 1000.0*stateOut[3:6] - scObject.hub.v_CN_NInit = (velStart.reshape(3,1)).tolist() + scObject.hub.r_CN_NInit = (1000.0 * stateOut[0:3].reshape(3, 1)).tolist() + velStart = 1000.0 * stateOut[3:6] + scObject.hub.v_CN_NInit = (velStart.reshape(3, 1)).tolist() scObject.hub.sigma_BNInit = [[0.0], [0.0], [0.0]] scObject.hub.omega_BN_BInit = [[0.001], [-0.002], [0.003]] @@ -139,53 +149,69 @@ def singleGravityBody(show_plots): posArray = [] velArray = [] posError = [] - while(currentTime < totalTime): + while currentTime < totalTime: unitTestSim.ConfigureStopTime(macros.sec2nano(currentTime + dt)) unitTestSim.ExecuteSimulation() - stateOut = spkRead('HUBBLE SPACE TELESCOPE', gravFactory.spiceObject.getCurrentTimeString(), 'J2000', 'EARTH') + stateOut = spkRead( + "HUBBLE SPACE TELESCOPE", + gravFactory.spiceObject.getCurrentTimeString(), + "J2000", + "EARTH", + ) posCurr = posRef.getState() posCurr = [y for x in posCurr for y in x] posArray.append(posCurr) velCurr = velRef.getState() velCurr = [y for x in velCurr for y in x] velArray.append(velCurr) - posDiff = numpy.array(posCurr) - stateOut[0:3]*1000.0 - posRow = [unitTestSim.TotalSim.CurrentNanos*1.0E-9] + posDiff = numpy.array(posCurr) - stateOut[0:3] * 1000.0 + posRow = [unitTestSim.TotalSim.CurrentNanos * 1.0e-9] posRow.extend(posDiff.tolist()) posError.append(posRow) assert numpy.linalg.norm(posDiff) < 1000.0 currentTime += dt - stateOut = spkRead('HUBBLE SPACE TELESCOPE', gravFactory.spiceObject.getCurrentTimeString(), 'J2000', 'EARTH') + stateOut = spkRead( + "HUBBLE SPACE TELESCOPE", + gravFactory.spiceObject.getCurrentTimeString(), + "J2000", + "EARTH", + ) posArray = numpy.array(posArray) posError = numpy.array(posError) gravFactory.unloadSpiceKernels() - pyswice.unload_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.unload_c(path + '/../_UnitTest/hst_edited.bsp') - print(numpy.max(abs(posError[:,1:4]))) + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + pyswice.unload_c(str(de430_path)) + pyswice.unload_c(str(naif0012_path)) + pyswice.unload_c(str(de403masses_path)) + pyswice.unload_c(str(pck00010_path)) + pyswice.unload_c(str(hst_edited_path)) + + print(numpy.max(abs(posError[:, 1:4]))) if show_plots: plt.close("all") plt.figure() plt.plot(posError[:, 0], posError[:, 1:4]) - plt.xlabel('Time (s)') - plt.ylabel('Position Difference (m)') + plt.xlabel("Time (s)") + plt.ylabel("Position Difference (m)") plt.show() plt.close("all") - if testFailCount == 0: print("PASSED: " + " Single body with spherical harmonics") # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] + def multiBodyGravity(show_plots): # The __tracebackhide__ setting influences pytest showing of tracebacks: @@ -208,18 +234,24 @@ def multiBodyGravity(show_plots): DynUnitTestProc = unitTestSim.CreateNewProcess(unitProcessName) # create the dynamics task and specify the integration update time - DynUnitTestProc.addTask(unitTestSim.CreateNewTask(unitTaskName, macros.sec2nano(5.0))) + DynUnitTestProc.addTask( + unitTestSim.CreateNewTask(unitTaskName, macros.sec2nano(5.0)) + ) # setup Gravity Bodies gravFactory = simIncludeGravBody.gravBodyFactory() - gravBodies = gravFactory.createBodies(['earth', 'mars barycenter', 'sun', 'moon', 'jupiter barycenter']) - gravBodies['sun'].isCentralBody = True + gravBodies = gravFactory.createBodies( + ["earth", "mars barycenter", "sun", "moon", "jupiter barycenter"] + ) + gravBodies["sun"].isCentralBody = True stringCurrent = "2008 September 19, 04:00:00.0" - gravFactory.createSpiceInterface(bskPath +'/supportData/EphemerisData/', stringCurrent) - gravFactory.spiceObject.zeroBase = 'Earth' + gravFactory.createSpiceInterface(time=stringCurrent) + gravFactory.spiceObject.zeroBase = "Earth" - scObject.gravField.gravBodies = spacecraft.GravBodyVector(list(gravFactory.gravBodies.values())) + scObject.gravField.gravBodies = spacecraft.GravBodyVector( + list(gravFactory.gravBodies.values()) + ) unitTestSim.AddModelToTask(unitTaskName, gravFactory.spiceObject, 10) @@ -228,22 +260,27 @@ def multiBodyGravity(show_plots): # separate from the earlier SPICE setup that was loaded to BSK. This is why # all required SPICE libraries must be included when setting up and loading # SPICE kernels in Python. - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.furnsh_c(path + '/../_UnitTest/nh_pred_od077.bsp') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + nh_pred_od077_path = get_path(DataFile.EphemerisData.nh_pred_od077) + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(naif0012_path)) + pyswice.furnsh_c(str(de403masses_path)) + pyswice.furnsh_c(str(pck00010_path)) + pyswice.furnsh_c(str(nh_pred_od077_path)) unitTestSim.AddModelToTask(unitTaskName, scObject, 9) - stateOut = spkRead('NEW HORIZONS', stringCurrent, 'J2000', 'SUN') + stateOut = spkRead("NEW HORIZONS", stringCurrent, "J2000", "SUN") scObject.hub.mHub = 100 scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] scObject.hub.IHubPntBc_B = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] - scObject.hub.r_CN_NInit = (1000.0*stateOut[0:3].reshape(3,1)).tolist() - velStart = 1000.0*stateOut[3:6] - scObject.hub.v_CN_NInit = (velStart.reshape(3,1)).tolist() + scObject.hub.r_CN_NInit = (1000.0 * stateOut[0:3].reshape(3, 1)).tolist() + velStart = 1000.0 * stateOut[3:6] + scObject.hub.v_CN_NInit = (velStart.reshape(3, 1)).tolist() scObject.hub.sigma_BNInit = [[0.0], [0.0], [0.0]] scObject.hub.omega_BN_BInit = [[0.001], [-0.002], [0.003]] @@ -262,53 +299,62 @@ def multiBodyGravity(show_plots): while currentTime < totalTime: unitTestSim.ConfigureStopTime(macros.sec2nano(currentTime + dt)) unitTestSim.ExecuteSimulation() - timeString = pyswice.et2utc_c(gravFactory.spiceObject.J2000Current, 'C', 4, 1024, "Yo") - stateOut = spkRead('NEW HORIZONS', timeString, 'J2000', 'SUN') + timeString = pyswice.et2utc_c( + gravFactory.spiceObject.J2000Current, "C", 4, 1024, "Yo" + ) + stateOut = spkRead("NEW HORIZONS", timeString, "J2000", "SUN") posCurr = posRef.getState() posCurr = [y for x in posCurr for y in x] posArray.append(posCurr) velCurr = velRef.getState() velCurr = [y for x in velCurr for y in x] velArray.append(velCurr) - posDiff = numpy.array(posCurr) - stateOut[0:3]*1000.0 - posRow = [unitTestSim.TotalSim.CurrentNanos*1.0E-9] + posDiff = numpy.array(posCurr) - stateOut[0:3] * 1000.0 + posRow = [unitTestSim.TotalSim.CurrentNanos * 1.0e-9] posRow.extend(posDiff.tolist()) posError.append(posRow) assert numpy.linalg.norm(posDiff) < 1000.0 - if currentTime > 0.0 + dt/2.0: - posJump = stateOut[0:3]*1000.0 - numpy.array(posPrevious) + if currentTime > 0.0 + dt / 2.0: + posJump = stateOut[0:3] * 1000.0 - numpy.array(posPrevious) posInc.append(posJump.tolist()) - posPrevious = stateOut[0:3]*1000.0 + posPrevious = stateOut[0:3] * 1000.0 currentTime += dt - stateOut = spkRead('NEW HORIZONS', gravFactory.spiceObject.getCurrentTimeString(), 'J2000', 'SUN') + stateOut = spkRead( + "NEW HORIZONS", gravFactory.spiceObject.getCurrentTimeString(), "J2000", "SUN" + ) posArray = numpy.array(posArray) posError = numpy.array(posError) posInc = numpy.array(posInc) gravFactory.unloadSpiceKernels() - pyswice.unload_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/naif0012.tls') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/de-403-masses.tpc') - pyswice.unload_c(bskPath + '/supportData/EphemerisData/pck00010.tpc') - pyswice.unload_c(path + '/../_UnitTest/nh_pred_od077.bsp') + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + de403masses_path = get_path(DataFile.EphemerisData.de_403_masses) + pck00010_path = get_path(DataFile.EphemerisData.pck00010) + nh_pred_od077_path = get_path(DataFile.EphemerisData.nh_pred_od077) + pyswice.unload_c(str(de430_path)) + pyswice.unload_c(str(naif0012_path)) + pyswice.unload_c(str(de403masses_path)) + pyswice.unload_c(str(pck00010_path)) + pyswice.unload_c(str(nh_pred_od077_path)) plt.close("all") plt.figure() - plt.plot(posError[:,0], posError[:,1:4]) - plt.xlabel('Time (s)') - plt.ylabel('Position Difference (m)') + plt.plot(posError[:, 0], posError[:, 1:4]) + plt.xlabel("Time (s)") + plt.ylabel("Position Difference (m)") - if(show_plots): + if show_plots: plt.show() - plt.close('all') + plt.close("all") if testFailCount == 0: print("PASSED: " + " multi-point source bodies") # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] def polyGravityBody(show_plots): @@ -322,10 +368,10 @@ def polyGravityBody(show_plots): testMessages = [] # create empty list to store test log messages # Obtain validation data (simulation with tight integration tolerances in MATLAB) - valData = numpy.genfromtxt(path + '/../_UnitTest/polyTestData.csv', delimiter=',') - tVal = numpy.array(valData[:,0]) - posVal = numpy.array(valData[:,1:4]) - velVal = numpy.array(valData[:,4:7]) + valData = numpy.genfromtxt(path + "/../_UnitTest/polyTestData.csv", delimiter=",") + tVal = numpy.array(valData[:, 0]) + posVal = numpy.array(valData[:, 1:4]) + velVal = numpy.array(valData[:, 4:7]) # Create a sim module as an empty container unitTaskName = "unitTask" # arbitrary name (don't change) @@ -337,16 +383,18 @@ def polyGravityBody(show_plots): DynUnitTestProc = unitTestSim.CreateNewProcess(unitProcessName) # create the dynamics task and specify the integration update time intTime = 30.0 - DynUnitTestProc.addTask(unitTestSim.CreateNewTask(unitTaskName, macros.sec2nano(intTime))) + DynUnitTestProc.addTask( + unitTestSim.CreateNewTask(unitTaskName, macros.sec2nano(intTime)) + ) # specify orbit of polyhedral body oePolyBody = planetEphemeris.ClassicElements() oePolyBody.a = 2.3612 * orbitalMotion.AU * 1000 oePolyBody.e = 0 - oePolyBody.i = 0*macros.D2R - oePolyBody.Omega = 0*macros.D2R - oePolyBody.omega = 0*macros.D2R - oePolyBody.f = 0*macros.D2R + oePolyBody.i = 0 * macros.D2R + oePolyBody.Omega = 0 * macros.D2R + oePolyBody.omega = 0 * macros.D2R + oePolyBody.f = 0 * macros.D2R raPolyBody = 0 * macros.D2R decPolyBody = 90 * macros.D2R @@ -355,7 +403,7 @@ def polyGravityBody(show_plots): # setup celestial object ephemeris module polyBodyEphem = planetEphemeris.PlanetEphemeris() - polyBodyEphem.ModelTag = 'erosEphemeris' + polyBodyEphem.ModelTag = "erosEphemeris" polyBodyEphem.setPlanetNames(planetEphemeris.StringVector(["eros"])) # specify celestial objects orbit @@ -365,14 +413,16 @@ def polyGravityBody(show_plots): polyBodyEphem.rightAscension = planetEphemeris.DoubleVector([raPolyBody]) polyBodyEphem.declination = planetEphemeris.DoubleVector([decPolyBody]) polyBodyEphem.lst0 = planetEphemeris.DoubleVector([lst0PolyBody]) - polyBodyEphem.rotRate = planetEphemeris.DoubleVector([360 * macros.D2R / rotPeriodPolyBody]) + polyBodyEphem.rotRate = planetEphemeris.DoubleVector( + [360 * macros.D2R / rotPeriodPolyBody] + ) # setup polyhedral gravity body mu = 4.46275472004 * 1e5 gravFactory = simIncludeGravBody.gravBodyFactory() - polyBody = gravFactory.createCustomGravObject('eros', mu=mu) + polyBody = gravFactory.createCustomGravObject("eros", mu=mu) polyBody.isCentralBody = True - polyBody.usePolyhedralGravityModel(path + '/../_UnitTest/EROS856Vert1708Fac.txt') + polyBody.usePolyhedralGravityModel(path + "/../_UnitTest/EROS856Vert1708Fac.txt") polyBody.planetBodyInMsg.subscribeTo(polyBodyEphem.planetOutMsgs[0]) # create an ephemeris converter @@ -383,12 +433,14 @@ def polyGravityBody(show_plots): # create spacecraft and attach polyhedral body scObject = spacecraft.Spacecraft() scObject.ModelTag = "spacecraft" - scObject.gravField.gravBodies = spacecraft.GravBodyVector(list(gravFactory.gravBodies.values())) + scObject.gravField.gravBodies = spacecraft.GravBodyVector( + list(gravFactory.gravBodies.values()) + ) # set initial conditions for spacecraft - angvelPolyBody = np.array([0,0,360 * macros.D2R / rotPeriodPolyBody]) - posInit = posVal[0,0:3] - velInit = velVal[0,0:3] + np.cross(angvelPolyBody, posInit) + angvelPolyBody = np.array([0, 0, 360 * macros.D2R / rotPeriodPolyBody]) + posInit = posVal[0, 0:3] + velInit = velVal[0, 0:3] + np.cross(angvelPolyBody, posInit) scObject.hub.r_CN_NInit = posInit.tolist() scObject.hub.v_CN_NInit = velInit.tolist() @@ -397,11 +449,13 @@ def polyGravityBody(show_plots): unitTestSim.AddModelToTask(unitTaskName, polyBodyEphemConverter, ModelPriority=9) unitTestSim.AddModelToTask(unitTaskName, scObject, ModelPriority=8) - totalTime = 24*3600 + totalTime = 24 * 3600 samplingTime = 300 scRec = scObject.scStateOutMsg.recorder(macros.sec2nano(samplingTime)) - polyBodyRec = polyBodyEphemConverter.ephemOutMsgs[0].recorder(macros.sec2nano(samplingTime)) + polyBodyRec = polyBodyEphemConverter.ephemOutMsgs[0].recorder( + macros.sec2nano(samplingTime) + ) unitTestSim.AddModelToTask(unitTaskName, scRec) unitTestSim.AddModelToTask(unitTaskName, polyBodyRec) @@ -423,29 +477,29 @@ def polyGravityBody(show_plots): R_AN = RigidBodyKinematics.MRP2C(sigma_AN[ii][0:3]) # rotate position and velocity - posArray[ii,0:3] = R_AN.dot(numpy.subtract(r_BN_N[ii],r_AN_N[ii])) + posArray[ii, 0:3] = R_AN.dot(numpy.subtract(r_BN_N[ii], r_AN_N[ii])) # compute error in position and assert max error - posError = numpy.linalg.norm(posArray - posVal,axis=1) + posError = numpy.linalg.norm(posArray - posVal, axis=1) assert max(posError) < 10 print(max(posError)) plt.close("all") plt.figure() plt.plot(tVal, posArray - posVal) - plt.xlabel('Time (s)') - plt.ylabel('Position Difference (m)') + plt.xlabel("Time (s)") + plt.ylabel("Position Difference (m)") - if(show_plots): + if show_plots: plt.show() - plt.close('all') + plt.close("all") if testFailCount == 0: print("PASSED: " + " Single body with polyhedral shape") # return fail count and join into a single string all messages in the list # testMessage - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] if __name__ == "__main__": diff --git a/src/simulation/environment/TabularAtmosphere/_UnitTest/test_unitTestTabularAtmosphere.py b/src/simulation/environment/TabularAtmosphere/_UnitTest/test_unitTestTabularAtmosphere.py index f400e9e18f..2a0d5aec8f 100644 --- a/src/simulation/environment/TabularAtmosphere/_UnitTest/test_unitTestTabularAtmosphere.py +++ b/src/simulation/environment/TabularAtmosphere/_UnitTest/test_unitTestTabularAtmosphere.py @@ -41,12 +41,18 @@ from Basilisk.architecture import bskLogging from Basilisk.utilities import orbitalMotion from Basilisk.utilities.readAtmTable import readAtmTable +from Basilisk.utilities.supportDataTools.dataFetcher import ( + get_path, + DataFile, +) -@pytest.mark.parametrize("accuracy", [1e-12]) -@pytest.mark.parametrize("altitude", [42.0, 33.33333, 10000.0, -10.0]) # exact, interpolate, above, below -@pytest.mark.parametrize("useMinReach", [ True, False]) -@pytest.mark.parametrize("useMaxReach", [ True, False]) +@pytest.mark.parametrize("accuracy", [1e-12]) +@pytest.mark.parametrize( + "altitude", [42.0, 33.33333, 10000.0, -10.0] +) # exact, interpolate, above, below +@pytest.mark.parametrize("useMinReach", [True, False]) +@pytest.mark.parametrize("useMaxReach", [True, False]) def test_tabularAtmosphere(altitude, accuracy, useMinReach, useMaxReach): r""" **Validation Test Description** @@ -82,31 +88,34 @@ def test_tabularAtmosphere(altitude, accuracy, useMinReach, useMaxReach): """ # each test method requires a single assert method to be called - [testResults, testMessage] = tabularAtmosphereTestFunction(altitude, accuracy, useMinReach, useMaxReach) + [testResults, testMessage] = tabularAtmosphereTestFunction( + altitude, accuracy, useMinReach, useMaxReach + ) assert testResults < 1, testMessage + def tabularAtmosphereTestFunction(altitude, accuracy, useMinReach, useMaxReach): - testFailCount = 0 # zero unit test result counter - unitTaskName = "unitTask" # arbitrary name (don't change) - unitProcessName = "TestProcess" # arbitrary name (don't change) + testFailCount = 0 # zero unit test result counter + unitTaskName = "unitTask" # arbitrary name (don't change) + unitProcessName = "TestProcess" # arbitrary name (don't change) bskLogging.setDefaultLogLevel(bskLogging.BSK_WARNING) # Create a sim module as an empty container unitTestSim = SimulationBaseClass.SimBaseClass() # Create test thread - testProcessRate = macros.sec2nano(0.5) # update process rate update time + testProcessRate = macros.sec2nano(0.5) # update process rate update time testProc = unitTestSim.CreateNewProcess(unitProcessName) testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate)) # Construct algorithm and associated C++ container - module = tabularAtmosphere.TabularAtmosphere() # update with current values - module.ModelTag = "tabularAtmosphere" # update python name of test module + module = tabularAtmosphere.TabularAtmosphere() # update with current values + module.ModelTag = "tabularAtmosphere" # update python name of test module # define constants & load data r_eq = 6378136.6 - filename = bskPath + '/supportData/AtmosphereData/EarthGRAMNominal.txt' - altList, rhoList, tempList = readAtmTable(filename,'EarthGRAM') + atm_path = get_path(DataFile.AtmosphereData.EarthGRAMNominal) + altList, rhoList, tempList = readAtmTable(str(atm_path), "EarthGRAM") # assign constants & ref. data to module module.planetRadius = r_eq @@ -132,7 +141,7 @@ def tabularAtmosphereTestFunction(altitude, accuracy, useMinReach, useMaxReach): # setup orbit and simulation time r0 = r_eq + (altitude * 1000.0) # meters oe = orbitalMotion.ClassicElements() - mu = 0.3986004415E+15 # meters^3/s^2 + mu = 0.3986004415e15 # meters^3/s^2 oe.a = r0 oe.e = 0.0 oe.i = 45.0 * macros.D2R @@ -142,7 +151,9 @@ def tabularAtmosphereTestFunction(altitude, accuracy, useMinReach, useMaxReach): r0N, v0N = orbitalMotion.elem2rv(mu, oe) # create the input messages - scStateMsg = messaging.SCStatesMsgPayload() # Create a structure for the input message + scStateMsg = ( + messaging.SCStatesMsgPayload() + ) # Create a structure for the input message scStateMsg.r_BN_N = np.array(r0N) scInMsg = messaging.SCStatesMsg().write(scStateMsg) @@ -160,7 +171,7 @@ def tabularAtmosphereTestFunction(altitude, accuracy, useMinReach, useMaxReach): # NOTE: the total simulation time may be longer than this value. The # simulation is stopped at the next logging event on or after the # simulation end time. - unitTestSim.ConfigureStopTime(macros.sec2nano(1.0)) # seconds to stop simulation + unitTestSim.ConfigureStopTime(macros.sec2nano(1.0)) # seconds to stop simulation # Begin the simulation time run set above unitTestSim.ExecuteSimulation() @@ -180,37 +191,45 @@ def tabAtmoComp(val, xList, yList): else: for i, x in enumerate(xList): if x >= val: - x0 = xList[i-1] - y0 = yList[i-1] + x0 = xList[i - 1] + y0 = yList[i - 1] y1 = yList[i] - m = (y1 - y0)/(x - x0) + m = (y1 - y0) / (x - x0) out = y0 + (val - x0) * m return out # compute truth values trueDensity = tabAtmoComp(altitude * 1000, altList, rhoList) - print('\nmodule density: {0:.6e}'.format(densData[0])) - print('true density: {0:.6e}'.format(trueDensity)) + print("\nmodule density: {0:.6e}".format(densData[0])) + print("true density: {0:.6e}".format(trueDensity)) trueTemp = tabAtmoComp(altitude * 1000, altList, tempList) - print('\nmodule temperature: {0:.6e}'.format(tempData[0])) - print('true temperature: {0:.6e}\n'.format(trueTemp)) + print("\nmodule temperature: {0:.6e}".format(tempData[0])) + print("true temperature: {0:.6e}\n".format(trueTemp)) # compare truth values to module results if trueDensity != 0: - testFailCount = not unitTestSupport.isDoubleEqualRelative(densData[0], trueDensity, accuracy) + testFailCount = not unitTestSupport.isDoubleEqualRelative( + densData[0], trueDensity, accuracy + ) else: - testFailCount = not unitTestSupport.isDoubleEqual(densData[0], trueDensity, accuracy) + testFailCount = not unitTestSupport.isDoubleEqual( + densData[0], trueDensity, accuracy + ) if testFailCount == 0: testMessage = "density computed correctly" else: testMessage = "density computed incorrectly" # compare truth values to module results for temperature - if trueTemp != 0 : # needs checking - testFailCount = not unitTestSupport.isDoubleEqualRelative(tempData[0], trueTemp, accuracy) + if trueTemp != 0: # needs checking + testFailCount = not unitTestSupport.isDoubleEqualRelative( + tempData[0], trueTemp, accuracy + ) else: - testFailCount = not unitTestSupport.isDoubleEqual(tempData[0], trueTemp, accuracy) + testFailCount = not unitTestSupport.isDoubleEqual( + tempData[0], trueTemp, accuracy + ) if testFailCount == 0: testMessage += " and temperature computed correctly" else: @@ -229,8 +248,8 @@ def tabAtmoComp(val, xList, yList): # if __name__ == "__main__": test_tabularAtmosphere( - 10000.0, # altitude - 1e-12, # accuracy - True, - True - ) + 10000.0, # altitude + 1e-12, # accuracy + True, + True, + ) diff --git a/src/simulation/environment/albedo/_UnitTest/test_albedo.py b/src/simulation/environment/albedo/_UnitTest/test_albedo.py index 588a68f74b..7d0e84c19f 100644 --- a/src/simulation/environment/albedo/_UnitTest/test_albedo.py +++ b/src/simulation/environment/albedo/_UnitTest/test_albedo.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2020, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -36,17 +35,20 @@ from Basilisk.utilities import simIncludeGravBody from Basilisk.utilities import unitTestSupport from Basilisk.architecture import bskLogging +from Basilisk.utilities.supportDataTools.dataFetcher import DataFile, get_path bskPath = __path__[0] path = os.path.dirname(os.path.abspath(__file__)) + # uncomment this line if this test has an expected failure, adjust message as needed # @pytest.mark.xfail(True) -@pytest.mark.parametrize("planetCase", ['earth', 'mars']) -@pytest.mark.parametrize("modelType", ['ALBEDO_AVG_IMPLICIT', 'ALBEDO_AVG_EXPLICIT', 'ALBEDO_DATA']) +@pytest.mark.parametrize("planetCase", ["earth", "mars"]) +@pytest.mark.parametrize( + "modelType", ["ALBEDO_AVG_IMPLICIT", "ALBEDO_AVG_EXPLICIT", "ALBEDO_DATA"] +) @pytest.mark.parametrize("useEclipse", [True, False]) - def test_unitAlbedo(show_plots, planetCase, modelType, useEclipse): """ **Validation Test Description** @@ -71,7 +73,9 @@ def test_unitAlbedo(show_plots, planetCase, modelType, useEclipse): """ # each test method requires a single assert method to be called - [testResults, testMessage] = unitAlbedo(show_plots, planetCase, modelType, useEclipse) + [testResults, testMessage] = unitAlbedo( + show_plots, planetCase, modelType, useEclipse + ) assert testResults < 1, testMessage @@ -95,19 +99,24 @@ def unitAlbedo(show_plots, planetCase, modelType, useEclipse): # Albedo A1 albModule = albedo.Albedo() albModule.ModelTag = "Albedo_0" - if modelType == 'ALBEDO_DATA': - dataPath = os.path.abspath(bskPath + "/supportData/AlbedoData/") - if planetCase == 'earth': - fileName = "Earth_ALB_2018_CERES_All_10x10.csv" - else: - fileName = "Mars_ALB_TES_10x10.csv" + if modelType == "ALBEDO_DATA": + dataFile = ( + DataFile.AlbedoData.Earth_ALB_2018_CERES_All_10x10 + if planetCase == "earth" + else DataFile.AlbedoData.Mars_ALB_TES_10x10 + ) + data = get_path(dataFile) + fileName = data.name + dataPath = str(data.parent) albModule.addPlanetandAlbedoDataModel(planetInMsg, dataPath, fileName) else: ALB_avg = 0.5 numLat = 200 numLon = 400 - if modelType == 'ALBEDO_AVG_EXPLICIT': - albModule.addPlanetandAlbedoAverageModel(planetInMsg, ALB_avg, numLat, numLon) + if modelType == "ALBEDO_AVG_EXPLICIT": + albModule.addPlanetandAlbedoAverageModel( + planetInMsg, ALB_avg, numLat, numLon + ) else: albModule.addPlanetandAlbedoAverageModel(planetInMsg) @@ -118,15 +127,15 @@ def unitAlbedo(show_plots, planetCase, modelType, useEclipse): # Create dummy planet message planetPositionMsg = messaging.SpicePlanetStateMsgPayload() - planetPositionMsg.PositionVector = [0., 0., 0.] + planetPositionMsg.PositionVector = [0.0, 0.0, 0.0] gravFactory = simIncludeGravBody.gravBodyFactory() - if planetCase == 'earth': + if planetCase == "earth": planet = gravFactory.createEarth() - sunPositionMsg.PositionVector = [-om.AU * 1000., 0.0, 0.0] - elif planetCase == 'mars': + sunPositionMsg.PositionVector = [-om.AU * 1000.0, 0.0, 0.0] + elif planetCase == "mars": planet = gravFactory.createMars() - sunPositionMsg.PositionVector = [-1.5 * om.AU * 1000., 0.0, 0.0] + sunPositionMsg.PositionVector = [-1.5 * om.AU * 1000.0, 0.0, 0.0] planetPositionMsg.PlanetName = planetCase planetPositionMsg.J20002Pfix = np.identity(3) @@ -135,15 +144,15 @@ def unitAlbedo(show_plots, planetCase, modelType, useEclipse): # Create dummy spacecraft message scStateMsg = messaging.SCStatesMsgPayload() rSC = req + 6000 * 1000 # meters - alpha = 71. * macros.D2R + alpha = 71.0 * macros.D2R scStateMsg.r_BN_N = np.dot(rSC, [np.cos(alpha), np.sin(alpha), 0.0]) - scStateMsg.sigma_BN = [0., 0., 0.] + scStateMsg.sigma_BN = [0.0, 0.0, 0.0] # Albedo instrument configuration config1 = albedo.instConfig_t() - config1.fov = 80. * macros.D2R + config1.fov = 80.0 * macros.D2R config1.nHat_B = np.array([-np.cos(alpha), -np.sin(alpha), 0.0]) - config1.r_IB_B = np.array([0., 0., 0.]) + config1.r_IB_B = np.array([0.0, 0.0, 0.0]) albModule.addInstrumentConfig(config1) sunInMsg = messaging.SpicePlanetStateMsg().write(sunPositionMsg) @@ -166,15 +175,15 @@ def unitAlbedo(show_plots, planetCase, modelType, useEclipse): unitTestSim.TotalSim.SingleStepProcesses() # This pulls the actual data log from the simulation run. dataAlb0 = dataLog.albedoAtInstrument - errTol = 1E-12 - if planetCase == 'earth': - if modelType == 'ALBEDO_DATA': + errTol = 1e-12 + if planetCase == "earth": + if modelType == "ALBEDO_DATA": if useEclipse: truthAlb = 0.0022055492477917 else: truthAlb = 0.0022055492477917 else: - if modelType == 'ALBEDO_AVG_EXPLICIT': + if modelType == "ALBEDO_AVG_EXPLICIT": if useEclipse: truthAlb = 0.0041742091531996 else: @@ -185,13 +194,13 @@ def unitAlbedo(show_plots, planetCase, modelType, useEclipse): else: truthAlb = 0.002421222716229847 else: - if modelType == 'ALBEDO_DATA': + if modelType == "ALBEDO_DATA": if useEclipse: truthAlb = 0.0014001432717662 else: truthAlb = 0.0014001432717662 else: - if modelType == 'ALBEDO_AVG_EXPLICIT': + if modelType == "ALBEDO_AVG_EXPLICIT": if useEclipse: truthAlb = 0.0035681407388827 else: @@ -206,12 +215,15 @@ def unitAlbedo(show_plots, planetCase, modelType, useEclipse): testFailCount += 1 # print out success or failure message if testFailCount == 0: - print("PASSED: " + albModule.ModelTag) + print("PASSED: " + albModule.ModelTag) else: - print("Failed: " + albModule.ModelTag) - print("This test uses a relative accuracy value of " + str(errTol * 100) + " percent") + print("Failed: " + albModule.ModelTag) + print( + "This test uses a relative accuracy value of " + str(errTol * 100) + " percent" + ) + + return [testFailCount, "".join(testMessages)] - return [testFailCount, ''.join(testMessages)] def test_albedo_invalid_file(tmp_path): """Verify that Albedo model returns gracefully when file cannot be loaded. @@ -243,13 +255,16 @@ def test_albedo_invalid_file(tmp_path): albModule.spacecraftStateInMsg.subscribeTo(scInMsg) with pytest.raises(BasiliskError): - albModule.addPlanetandAlbedoDataModel(planetInMsg, str(tmp_path), "does_not_exist.file") + albModule.addPlanetandAlbedoDataModel( + planetInMsg, str(tmp_path), "does_not_exist.file" + ) albModule.Reset(0) # the fact that we got here without segfaulting means the test # passed assert True + if __name__ == "__main__": # unitAlbedo(False, 'earth', 'ALBEDO_AVG_EXPLICIT', True) - unitAlbedo(False, 'mars', 'ALBEDO_AVG_IMPLICIT', False) + unitAlbedo(False, "mars", "ALBEDO_AVG_IMPLICIT", False) diff --git a/src/simulation/environment/eclipse/_UnitTest/test_eclipse.py b/src/simulation/environment/eclipse/_UnitTest/test_eclipse.py index 047aa4d340..442e807531 100755 --- a/src/simulation/environment/eclipse/_UnitTest/test_eclipse.py +++ b/src/simulation/environment/eclipse/_UnitTest/test_eclipse.py @@ -157,12 +157,10 @@ def unitEclipse(show_plots, eclipseCondition, planet): scObject_0.gravField.gravBodies = spacecraft.GravBodyVector(list(gravFactory.gravBodies.values())) # setup Spice interface for some solar system bodies - timeInitString = '2021 MAY 04 07:47:48.965 (UTC)' - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/' - , timeInitString - # earth and mars must come first as with gravBodies - , spicePlanetNames=["earth", "mars barycenter", "sun", "venus"] - ) + timeInitString = "2021 MAY 04 07:47:48.965 (UTC)" + # earth and mars must come first as with gravBodies + gravFactory.createSpiceInterface(time=timeInitString, + spicePlanetNames=["earth", "mars barycenter", "sun", "venus"]) if planet == "earth": if eclipseCondition == "full": diff --git a/src/simulation/environment/groundLocation/_UnitTest/test_unitGroundLocation.py b/src/simulation/environment/groundLocation/_UnitTest/test_unitGroundLocation.py index c79a52d162..7263e6014b 100644 --- a/src/simulation/environment/groundLocation/_UnitTest/test_unitGroundLocation.py +++ b/src/simulation/environment/groundLocation/_UnitTest/test_unitGroundLocation.py @@ -224,9 +224,7 @@ def test_AzElR_rates(): mu = planet.mu planet.isCentralBody = True timeInitString = '2021 MAY 04 06:47:48.965 (UTC)' - gravFactory.createSpiceInterface(bskPath + '/supportData/EphemerisData/' - , timeInitString - ) + gravFactory.createSpiceInterface(time=timeInitString) gravFactory.spiceObject.zeroBase = 'Earth' scSim.AddModelToTask(simTaskName, gravFactory.spiceObject, -1) diff --git a/src/simulation/environment/spiceInterface/_UnitTest/test_unitSpiceSpacecraft.py b/src/simulation/environment/spiceInterface/_UnitTest/test_unitSpiceSpacecraft.py index 34aef5098d..800dd36ba0 100755 --- a/src/simulation/environment/spiceInterface/_UnitTest/test_unitSpiceSpacecraft.py +++ b/src/simulation/environment/spiceInterface/_UnitTest/test_unitSpiceSpacecraft.py @@ -1,4 +1,3 @@ - # ISC License # # Copyright (c) 2021, Autonomous Vehicle Systems Lab, University of Colorado at Boulder @@ -34,12 +33,15 @@ filename = inspect.getframeinfo(inspect.currentframe()).filename path = os.path.dirname(os.path.abspath(filename)) from Basilisk import __path__ + bskPath = __path__[0] from Basilisk.utilities import unitTestSupport from Basilisk.utilities import SimulationBaseClass from Basilisk.simulation import spiceInterface from Basilisk.utilities import macros +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + # provide a unique test method name, starting with test_ def test_unitSpiceSc(show_plots): @@ -69,12 +71,14 @@ def unitSpiceSc(show_plots): # Initialize the spice modules that we are using. spiceObject = spiceInterface.SpiceInterface() spiceObject.ModelTag = "SpiceInterfaceData" - spiceObject.SPICEDataPath = bskPath + '/supportData/EphemerisData/' + spiceObject.SPICEDataPath = bskPath + "/supportData/EphemerisData/" scNames = ["HUBBLE SPACE TELESCOPE"] spiceObject.addSpacecraftNames(spiceInterface.StringVector(scNames)) spiceObject.UTCCalInit = dateSpice spiceObject.zeroBase = "earth" - spiceObject.loadSpiceKernel("hst_edited.bsp", bskPath + '/supportData/EphemerisData/') + hst_edited_path = get_path(DataFile.EphemerisData.hst_edited) + kernel_dir = str(hst_edited_path.parent) + "/" + spiceObject.loadSpiceKernel(hst_edited_path.name, kernel_dir) TotalSim.AddModelToTask(unitTaskName, spiceObject) @@ -86,67 +90,114 @@ def unitSpiceSc(show_plots): TotalSim.ExecuteSimulation() # unload spice kernel - spiceObject.unloadSpiceKernel("hst_edited.bsp", bskPath + '/supportData/EphemerisData/') + spiceObject.unloadSpiceKernel(hst_edited_path.name, kernel_dir) # set truth - truthPosition = np.array([-5855529.540348052, 1986110.860522791, -3116764.7117067943]) - truthVelocity = np.array([-1848.9038338503085, -7268.515626753905, -1155.3578832725618]) - truthAtt = np.array([0., 0., 0.]) - truthZero = np.array([0., 0., 0.]) + truthPosition = np.array( + [-5855529.540348052, 1986110.860522791, -3116764.7117067943] + ) + truthVelocity = np.array( + [-1848.9038338503085, -7268.515626753905, -1155.3578832725618] + ) + truthAtt = np.array([0.0, 0.0, 0.0]) + truthZero = np.array([0.0, 0.0, 0.0]) scStateMsg = spiceObject.scStateOutMsgs[0].read() # print(scStateMsg.r_BN_N) # print(scStateMsg.v_BN_N) # print(scStateMsg.sigma_BN) accuracy = 0.01 - testFailCount, testMessages = unitTestSupport.compareVector(truthPosition, - scStateMsg.r_BN_N, - accuracy, "scState-r_BN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthPosition, - scStateMsg.r_CN_N, - accuracy, "scState-r_CN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthVelocity, - scStateMsg.v_BN_N, - accuracy, "scState-v_BN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthVelocity, - scStateMsg.v_CN_N, - accuracy, "scState-v_CN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthAtt, - scStateMsg.sigma_BN, - accuracy, "scState-sigma_BN", - testFailCount, testMessages) + testFailCount, testMessages = unitTestSupport.compareVector( + truthPosition, + scStateMsg.r_BN_N, + accuracy, + "scState-r_BN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthPosition, + scStateMsg.r_CN_N, + accuracy, + "scState-r_CN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthVelocity, + scStateMsg.v_BN_N, + accuracy, + "scState-v_BN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthVelocity, + scStateMsg.v_CN_N, + accuracy, + "scState-v_CN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthAtt, + scStateMsg.sigma_BN, + accuracy, + "scState-sigma_BN", + testFailCount, + testMessages, + ) attStateMsg = spiceObject.attRefStateOutMsgs[0].read() - testFailCount, testMessages = unitTestSupport.compareVector(truthAtt, - attStateMsg.sigma_RN, - accuracy, "scState-sigma_RN", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthZero, - attStateMsg.omega_RN_N, - accuracy, "scState-omega_RN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthZero, - attStateMsg.domega_RN_N, - accuracy, "scState-domega_RN_N", - testFailCount, testMessages) + testFailCount, testMessages = unitTestSupport.compareVector( + truthAtt, + attStateMsg.sigma_RN, + accuracy, + "scState-sigma_RN", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthZero, + attStateMsg.omega_RN_N, + accuracy, + "scState-omega_RN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthZero, + attStateMsg.domega_RN_N, + accuracy, + "scState-domega_RN_N", + testFailCount, + testMessages, + ) transStateMsg = spiceObject.transRefStateOutMsgs[0].read() - testFailCount, testMessages = unitTestSupport.compareVector(truthPosition, - transStateMsg.r_RN_N, - accuracy, "scState-r_RN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthVelocity, - transStateMsg.v_RN_N, - accuracy, "scState-v_RN_N", - testFailCount, testMessages) - testFailCount, testMessages = unitTestSupport.compareVector(truthZero, - transStateMsg.a_RN_N, - accuracy, "scState-a_RN_N", - testFailCount, testMessages) - + testFailCount, testMessages = unitTestSupport.compareVector( + truthPosition, + transStateMsg.r_RN_N, + accuracy, + "scState-r_RN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthVelocity, + transStateMsg.v_RN_N, + accuracy, + "scState-v_RN_N", + testFailCount, + testMessages, + ) + testFailCount, testMessages = unitTestSupport.compareVector( + truthZero, + transStateMsg.a_RN_N, + accuracy, + "scState-a_RN_N", + testFailCount, + testMessages, + ) # print out success message if no error were found if testFailCount == 0: @@ -154,7 +205,7 @@ def unitSpiceSc(show_plots): # each test method requires a single assert method to be called # this check below just makes sure no sub-test failures were found - return [testFailCount, ''.join(testMessages)] + return [testFailCount, "".join(testMessages)] # This statement below ensures that the unit test scrip can be run as a @@ -162,5 +213,5 @@ def unitSpiceSc(show_plots): # if __name__ == "__main__": test_unitSpiceSc( - False # show_plots - ) + False # show_plots + ) diff --git a/src/simulation/mujocoDynamics/NBodyGravity/_UnitTest/test_gravity.py b/src/simulation/mujocoDynamics/NBodyGravity/_UnitTest/test_gravity.py index e5360d3dec..108b480532 100644 --- a/src/simulation/mujocoDynamics/NBodyGravity/_UnitTest/test_gravity.py +++ b/src/simulation/mujocoDynamics/NBodyGravity/_UnitTest/test_gravity.py @@ -39,7 +39,7 @@ from Basilisk.utilities import simIncludeGravBody from Basilisk.simulation import svIntegrators from Basilisk.simulation import spacecraft - +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile import numpy as np import matplotlib.pyplot as plt @@ -66,7 +66,7 @@ def test_pointMass(showPlots): """ # Orbital parameters for the body - mu = 0.3986004415e15 # m**3/s**2 + mu = 0.3986004415e15 # m**3/s**2 oe = orbitalMotion.ClassicElements() rLEO = 7000.0 * 1000 # meters @@ -80,7 +80,7 @@ def test_pointMass(showPlots): rN, vN = orbitalMotion.elem2rv(mu, oe) oe = orbitalMotion.rv2elem(mu, rN, vN) - period = 2 * np.pi * np.sqrt(oe.a**3 / mu) # s + period = 2 * np.pi * np.sqrt(oe.a**3 / mu) # s tf = 2 * period # Because we use an adaptive integrator we can set the @@ -128,7 +128,7 @@ def test_pointMass(showPlots): # Add random attitude and attitude rate, which should have no impact body.setAttitude(rbk.euler1232MRP([np.pi / 2, np.pi / 6, np.pi / 4])) - body.setAttitudeRate([0.3, 0.1, 0.2]) # rad/s + body.setAttitudeRate([0.3, 0.1, 0.2]) # rad/s # Run sim scSim.ConfigureStopTime(macros.sec2nano(tf)) @@ -211,7 +211,7 @@ def test_dumbbell(showPlots, initialAngularRate): """ # Initial orbital parameters of the body: circular equatorial LEO orbit - mu = 0.3986004415e15 # m**3/s**2 + mu = 0.3986004415e15 # m**3/s**2 oe = orbitalMotion.ClassicElements() rLEO = 7000.0 * 1000 # meters @@ -225,7 +225,7 @@ def test_dumbbell(showPlots, initialAngularRate): rN, vN = orbitalMotion.elem2rv(mu, oe) oe = orbitalMotion.rv2elem(mu, rN, vN) - period = 2 * np.pi * np.sqrt(oe.a**3 / mu) # s + period = 2 * np.pi * np.sqrt(oe.a**3 / mu) # s # Integrate for an orbit, record 50 points through orbit tf = period * 1 @@ -285,7 +285,9 @@ def test_dumbbell(showPlots, initialAngularRate): mainBody.setPosition(rN) mainBody.setVelocity(vN) mainBody.setAttitude(rbk.euler1232MRP([0, 0, 0])) - mainBody.setAttitudeRate([0, 0, 2 * np.pi / period if initialAngularRate else 0]) # rad/s + mainBody.setAttitudeRate( + [0, 0, 2 * np.pi / period if initialAngularRate else 0] + ) # rad/s # Run sim scSim.ExecuteSimulation() @@ -393,9 +395,12 @@ def loadSatelliteTrajectory(utcCalInit: str, stopTimeSeconds: float, dtSeconds: """Loads the trajectory of a GPS satellite from SPICE kernels from the given date (``utcCalInit``), for ``stopTimeSeconds`` seconds, with the position reported every ``dtSeconds`` seconds.""" + + de430_path = get_path(DataFile.EphemerisData.de430) + naif0012_path = get_path(DataFile.EphemerisData.naif0012) kernels = [ - bskPath + "/supportData/EphemerisData/de430.bsp", - bskPath + "/supportData/EphemerisData/naif0012.tls", + str(de430_path), + str(naif0012_path), path + "/gnss_221111_221123_v01.bsp", ] @@ -452,8 +457,8 @@ def test_gps(showPlots: bool, useSphericalHarmonics: bool, useThirdBodies: bool) # initial date, simulation time, and time step utcCalInit = "2022 NOV 14 00:01:10" - tf = 1 * 3600 # s - dt = 1 # s + tf = 1 * 3600 # s + dt = 1 # s # load trajectory from SPICE spiceSatelliteState = loadSatelliteTrajectory(utcCalInit, tf, dt) @@ -490,9 +495,8 @@ def test_gps(showPlots: bool, useSphericalHarmonics: bool, useThirdBodies: bool) sphericalHarmonicsGravityModel.SphericalHarmonicsGravityModel() ) earthGravityModel.radEquator = 0.6378136300e7 - earthGravityModel.loadFromFile( - bskPath + "/supportData/LocalGravData/GGM03S.txt", 4 - ) # J4 should be plenty + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + earthGravityModel.loadFromFile(str(ggm03s_path), 4) # J4 should be plenty else: earthGravityModel = pointMassGravityModel.PointMassGravityModel() earthGravityModel.muBody = 0.3986004415e15 @@ -533,8 +537,8 @@ def test_gps(showPlots: bool, useSphericalHarmonics: bool, useThirdBodies: bool) scSim.ConfigureStopTime(macros.sec2nano(tf)) # Initialize the body to the same position and velocity as the SPICE result - body.setPosition(spiceSatelliteState[0, :3] * 1000) # m - body.setVelocity(spiceSatelliteState[0, 3:] * 1000) # m + body.setPosition(spiceSatelliteState[0, :3] * 1000) # m + body.setVelocity(spiceSatelliteState[0, 3:] * 1000) # m # Run sim scSim.ExecuteSimulation() @@ -606,11 +610,11 @@ def test_mujocoVsSpacecraft( # Initial time, final time, and time step # We run for 24hr only if we want to plot results utcCalInit = "2022 NOV 14 00:01:10" - dt = 1 # s - tf = 24 * 3600 if showPlots else 10 # s + dt = 1 # s + tf = 24 * 3600 if showPlots else 10 # s # initial state of the body - mu = 0.3986004415e15 # m**3/s**2 + mu = 0.3986004415e15 # m**3/s**2 oe = orbitalMotion.ClassicElements() rLEO = 7000.0 * 1000 # meters @@ -632,9 +636,8 @@ def test_mujocoVsSpacecraft( gravBodies = gravFactory.createBodies(bodies) gravBodies["earth"].isCentralBody = True if useSphericalHarmonics: - gravBodies["earth"].useSphericalHarmonicsGravityModel( - bskPath + "/supportData/LocalGravData/GGM03S.txt", 4 - ) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S) + gravBodies["earth"].useSphericalHarmonicsGravityModel(str(ggm03s_path), 4) # A decorator that times how long the function takes and # prints it to the console @@ -643,7 +646,7 @@ def wrap(): tic = time.time() result = fn() toc = time.time() - print(f"{fn.__name__} took {toc-tic} seconds") + print(f"{fn.__name__} took {toc - tic} seconds") return result return wrap @@ -664,9 +667,7 @@ def spacecraftSim(): # Create spice interface and add to task if useSpice: - gravFactory.createSpiceInterface( - bskPath + "/supportData/EphemerisData/", utcCalInit - ) + gravFactory.createSpiceInterface(time=utcCalInit) gravFactory.spiceObject.zeroBase = "Earth" scSim.AddModelToTask("test", gravFactory.spiceObject, 10) @@ -682,11 +683,15 @@ def spacecraftSim(): ) # initialize spacecraft parameters - scObject.hub.mHub = 100 # kg - scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] # m - scObject.hub.IHubPntBc_B = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] # kg*m**2 - scObject.hub.r_CN_NInit = rN # m - scObject.hub.v_CN_NInit = vN # m/s + scObject.hub.mHub = 100 # kg + scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] # m + scObject.hub.IHubPntBc_B = [ + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ] # kg*m**2 + scObject.hub.r_CN_NInit = rN # m + scObject.hub.v_CN_NInit = vN # m/s # Create recorder scStateRecorder = scObject.scStateOutMsg.recorder() @@ -757,7 +762,6 @@ def mujocoSim(): bodyStateRecorder = mujocoSim() if showPlots: - t = bodyStateRecorder.times() * macros.NANO2SEC diff = np.linalg.norm(scStateRecorder.r_BN_N - bodyStateRecorder.r_BN_N, axis=1) diff --git a/src/tests/test_dataFetcher.py b/src/tests/test_dataFetcher.py new file mode 100644 index 0000000000..2cb81f848e --- /dev/null +++ b/src/tests/test_dataFetcher.py @@ -0,0 +1,68 @@ +# +# ISC License +# +# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at 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. +# + +""" +Unit tests for Basilisk supportData fetching (Pooch + local fallback). +""" + +import pytest +from pathlib import Path +from unittest.mock import patch + +from Basilisk.utilities.supportDataTools.dataFetcher import ( + DataFile, + get_path, + relpath, + REGISTRY, +) + + +@pytest.fixture +def fake_fetch(monkeypatch, tmp_path): + """Patch POOCH.fetch to always return a small dummy file.""" + dummy = tmp_path / "dummy.dat" + dummy.write_text("test") + + monkeypatch.setattr( + "Basilisk.utilities.supportDataTools.dataFetcher.POOCH.fetch", + lambda key: str(dummy), + ) + + return dummy + + +def test_all_enum_entries_are_in_registry(): + """Every DataFile enum value must correspond to a key in REGISTRY.""" + missing = [] + + for category in DataFile.__dict__.values(): + if isinstance(category, type) and hasattr(category, "__members__"): + for enum_value in category: + key = relpath(enum_value) + if key not in REGISTRY: + missing.append(key) + + assert not missing, f"Registry missing entries: {missing}" + + +def test_get_path_uses_pooch_when_not_local(fake_fetch): + """get_path() must return a Path backed by mocked Pooch.""" + path = get_path(DataFile.MagneticFieldData.WMM) + + assert isinstance(path, Path) + assert path.exists(), "get_path() should return an existing dummy file" diff --git a/src/utilities/MonteCarlo/_UnitTests/test_scenarioBasicOrbitMC.py b/src/utilities/MonteCarlo/_UnitTests/test_scenarioBasicOrbitMC.py index db56f87432..964b391e4c 100644 --- a/src/utilities/MonteCarlo/_UnitTests/test_scenarioBasicOrbitMC.py +++ b/src/utilities/MonteCarlo/_UnitTests/test_scenarioBasicOrbitMC.py @@ -27,16 +27,24 @@ # sys.path.append(bskPath + 'PythonModules') from Basilisk import __path__ + bskPath = __path__[0] from Basilisk.utilities.MonteCarlo.Controller import Controller, RetentionPolicy -from Basilisk.utilities.MonteCarlo.Dispersions import UniformEulerAngleMRPDispersion, UniformDispersion, NormalVectorCartDispersion, OrbitalElementDispersion +from Basilisk.utilities.MonteCarlo.Dispersions import ( + UniformEulerAngleMRPDispersion, + UniformDispersion, + NormalVectorCartDispersion, + OrbitalElementDispersion, +) + # import simulation related support from Basilisk.simulation import spacecraft from Basilisk.utilities import orbitalMotion from Basilisk.utilities import simIncludeGravBody from Basilisk.utilities import macros from Basilisk.utilities import SimulationBaseClass +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile import shutil import matplotlib.pyplot as plt import numpy as np @@ -51,8 +59,9 @@ var1 = "v_BN_N" var2 = "r_BN_N" + def myCreationFunction(): - """ function that returns a simulation """ + """function that returns a simulation""" # Create a sim module as an empty container sim = SimulationBaseClass.SimBaseClass() @@ -64,7 +73,7 @@ def myCreationFunction(): dynProcess = sim.CreateNewProcess(simProcessName) # Create the dynamics task and specify the integration update time - simulationTimeStep = macros.sec2nano(10.) + simulationTimeStep = macros.sec2nano(10.0) dynProcess.addTask(sim.CreateNewTask(simTaskName, simulationTimeStep)) # Setup the simulation modules @@ -78,22 +87,25 @@ def myCreationFunction(): gravFactory = simIncludeGravBody.gravBodyFactory() planet = gravFactory.createEarth() planet.isCentralBody = True - planet.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S-J2-only.txt' - , 2 - ) + ggm03s_path = get_path(DataFile.LocalGravData.GGM03S_J2_only) + planet.useSphericalHarmonicsGravityModel(str(ggm03s_path), 2) scObject.gravField.gravBodies = spacecraft.GravBodyVector([planet]) # Setup the orbit using classical orbit elements oe = orbitalMotion.ClassicElements() - rGEO = 42000. * 1000 # meters + rGEO = 42000.0 * 1000 # meters oe.a = rGEO oe.e = 0.00001 oe.i = 0.0 * macros.D2R oe.Omega = 48.2 * macros.D2R oe.omega = 347.8 * macros.D2R oe.f = 85.3 * macros.D2R - rN, vN = orbitalMotion.elem2rv(planet.mu, oe) # this stores consistent initial orbit elements - oe = orbitalMotion.rv2elem(planet.mu, rN, vN) # with circular or equatorial orbit, some angles are arbitrary + rN, vN = orbitalMotion.elem2rv( + planet.mu, oe + ) # this stores consistent initial orbit elements + oe = orbitalMotion.rv2elem( + planet.mu, rN, vN + ) # with circular or equatorial orbit, some angles are arbitrary # initialize Spacecraft States with the initialization variables scObject.hub.r_CN_NInit = rN # m - r_BN_N @@ -101,7 +113,7 @@ def myCreationFunction(): # set the simulation time mean_motion = np.sqrt(planet.mu / oe.a / oe.a / oe.a) - period = 2. * np.pi / mean_motion + period = 2.0 * np.pi / mean_motion simulationTime = macros.sec2nano(period / 4) sim.msgRecList = {} @@ -115,21 +127,27 @@ def myCreationFunction(): def myExecutionFunction(sim): - """ function that executes a simulation """ + """function that executes a simulation""" sim.InitializeSimulation() sim.ExecuteSimulation() -colorList = ['b', 'r', 'g', 'k'] + +colorList = ["b", "r", "g", "k"] + def myDataCallback(monteCarloData, retentionPolicy): data = np.array(monteCarloData["messages"][retainedMessageName + ".r_BN_N"]) - plt.plot(data[:, 1], data[:, 2], colorList[monteCarloData["index"]], label="run " + str(monteCarloData["index"])) - plt.xlabel('X-coordinate') - plt.ylabel('Y-coordinate') + plt.plot( + data[:, 1], + data[:, 2], + colorList[monteCarloData["index"]], + label="run " + str(monteCarloData["index"]), + ) + plt.xlabel("X-coordinate") + plt.ylabel("Y-coordinate") plt.legend() - @pytest.mark.slowtest def test_MonteCarloSimulation(show_plots): # Test a montecarlo simulation @@ -144,15 +162,15 @@ def test_MonteCarloSimulation(show_plots): monteCarlo.setArchiveDir(dirName) # Add some dispersions - disp1Name = 'TaskList[0].TaskModels[0].hub.sigma_BNInit' - disp2Name = 'TaskList[0].TaskModels[0].hub.omega_BN_BInit' - disp3Name = 'TaskList[0].TaskModels[0].hub.mHub' - disp4Name = 'TaskList[0].TaskModels[0].hub.r_BcB_B' - disp5Name = 'TaskList[0].TaskModels[0].hub.r_CN_NInit' - disp6Name = 'TaskList[0].TaskModels[0].hub.v_CN_NInit' + disp1Name = "TaskList[0].TaskModels[0].hub.sigma_BNInit" + disp2Name = "TaskList[0].TaskModels[0].hub.omega_BN_BInit" + disp3Name = "TaskList[0].TaskModels[0].hub.mHub" + disp4Name = "TaskList[0].TaskModels[0].hub.r_BcB_B" + disp5Name = "TaskList[0].TaskModels[0].hub.r_CN_NInit" + disp6Name = "TaskList[0].TaskModels[0].hub.v_CN_NInit" dispDict = {} - dispDict["mu"] = 0.3986004415E+15 - dispDict["a"] = ["normal", 42000 * 1E3, 2000 * 1E3] + dispDict["mu"] = 0.3986004415e15 + dispDict["a"] = ["normal", 42000 * 1e3, 2000 * 1e3] dispDict["e"] = ["uniform", 0, 0.5] dispDict["i"] = ["uniform", -80, 80] dispDict["Omega"] = None @@ -160,10 +178,17 @@ def test_MonteCarloSimulation(show_plots): dispDict["f"] = ["uniform", 0, 359] monteCarlo.addDispersion(OrbitalElementDispersion(disp5Name, disp6Name, dispDict)) monteCarlo.addDispersion(UniformEulerAngleMRPDispersion(disp1Name)) - monteCarlo.addDispersion(NormalVectorCartDispersion(disp2Name, 0.0, 0.75 / 3.0 * np.pi / 180)) - monteCarlo.addDispersion(UniformDispersion(disp3Name, ([1300.0 - 812.3, 1500.0 - 812.3]))) monteCarlo.addDispersion( - NormalVectorCartDispersion(disp4Name, [0.0, 0.0, 1.0], [0.05 / 3.0, 0.05 / 3.0, 0.1 / 3.0])) + NormalVectorCartDispersion(disp2Name, 0.0, 0.75 / 3.0 * np.pi / 180) + ) + monteCarlo.addDispersion( + UniformDispersion(disp3Name, ([1300.0 - 812.3, 1500.0 - 812.3])) + ) + monteCarlo.addDispersion( + NormalVectorCartDispersion( + disp4Name, [0.0, 0.0, 1.0], [0.05 / 3.0, 0.05 / 3.0, 0.1 / 3.0] + ) + ) # Add retention policy retentionPolicy = RetentionPolicy() @@ -178,33 +203,42 @@ def test_MonteCarloSimulation(show_plots): # Test loading data from runs from disk monteCarloLoaded = Controller.load(dirName) - retainedData = monteCarloLoaded.getRetainedData(NUMBER_OF_RUNS-1) + retainedData = monteCarloLoaded.getRetainedData(NUMBER_OF_RUNS - 1) assert retainedData is not None, "Retained data should be available after execution" assert "messages" in retainedData, "Retained data should retain messages" - assert retainedMessageName + ".r_BN_N" in retainedData["messages"], "Retained messages should exist" - assert retainedMessageName + ".v_BN_N" in retainedData["messages"], "Retained messages should exist" + assert retainedMessageName + ".r_BN_N" in retainedData["messages"], ( + "Retained messages should exist" + ) + assert retainedMessageName + ".v_BN_N" in retainedData["messages"], ( + "Retained messages should exist" + ) # rerun the case and it should be the same, because we dispersed random seeds oldOutput = retainedData["messages"][retainedMessageName + ".r_BN_N"] - failed = monteCarloLoaded.reRunCases([NUMBER_OF_RUNS-1]) + failed = monteCarloLoaded.reRunCases([NUMBER_OF_RUNS - 1]) assert len(failed) == 0, "Should rerun case successfully" - retainedData = monteCarloLoaded.getRetainedData(NUMBER_OF_RUNS-1) + retainedData = monteCarloLoaded.getRetainedData(NUMBER_OF_RUNS - 1) newOutput = retainedData["messages"][retainedMessageName + ".r_BN_N"] for k1, v1 in enumerate(oldOutput): for k2, v2 in enumerate(v1): - assert np.fabs(oldOutput[k1][k2] - newOutput[k1][k2]) < .001, \ - "Outputs shouldn't change on runs if random seeds are same" + assert np.fabs(oldOutput[k1][k2] - newOutput[k1][k2]) < 0.001, ( + "Outputs shouldn't change on runs if random seeds are same" + ) # test the initial parameters were saved from runs, and they differ between runs - params1 = monteCarloLoaded.getParameters(NUMBER_OF_RUNS-1) - params2 = monteCarloLoaded.getParameters(NUMBER_OF_RUNS-2) - assert "TaskList[0].TaskModels[0].RNGSeed" in params1, "random number seed should be applied" + params1 = monteCarloLoaded.getParameters(NUMBER_OF_RUNS - 1) + params2 = monteCarloLoaded.getParameters(NUMBER_OF_RUNS - 2) + assert "TaskList[0].TaskModels[0].RNGSeed" in params1, ( + "random number seed should be applied" + ) for dispName in [disp1Name, disp2Name, disp3Name, disp4Name]: assert dispName in params1, "dispersion should be applied" # assert two different runs had different parameters. - assert params1[dispName] != params2[dispName], "dispersion should be different in each run" + assert params1[dispName] != params2[dispName], ( + "dispersion should be different in each run" + ) monteCarloLoaded.executeCallbacks() if show_plots: diff --git a/src/utilities/planetStates.py b/src/utilities/planetStates.py index d243bf410a..2a685f6f70 100644 --- a/src/utilities/planetStates.py +++ b/src/utilities/planetStates.py @@ -17,35 +17,67 @@ # +from pathlib import Path from Basilisk import __path__ from Basilisk.topLevelModules import pyswice from Basilisk.utilities.pyswice_spk_utilities import spkRead +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile bskPath = __path__[0] -def planetPositionVelocity(planetName, time, ephemerisPath = '/supportData/EphemerisData/pck00010.tpc', observer = 'SSB', frame = 'J2000'): + +def planetPositionVelocity( + planetName, + time, + ephemerisPath="/supportData/EphemerisData/pck00010.tpc", + observer="SSB", + frame="J2000", +): """ - A convenience function to get planet position from spice - - Parameters - ---------- - planetName : name of planet to get position of - planet name must be a valid SPICE celestial body string. - time : UTC time as string - ephemerisPath : a string path to ephemeris file if something other than the default is desired - observer : observer to get vectors relative to - - Returns - ------- - position and velocity vector of planet in Solar System Barycenter inertial frame as lists [m], [m/s] + Convenience function to get planet position from SPICE. """ - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/de430.bsp') - pyswice.furnsh_c(bskPath + '/supportData/EphemerisData/naif0012.tls') #load leap seconds - pyswice.furnsh_c(bskPath + ephemerisPath) + from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + from pathlib import Path + from Basilisk.topLevelModules import pyswice + from Basilisk.utilities.pyswice_spk_utilities import spkRead + + # --- Resolve kernel paths --- + de430_path = Path(get_path(DataFile.EphemerisData.de430)) + naif0012_path = Path(get_path(DataFile.EphemerisData.naif0012)) + + print("\n==== planetPositionVelocity DEBUG ====") + print("planetName:", planetName) + print("time:", time) + print("ephemerisPath:", ephemerisPath) + print("de430_path:", de430_path) + print("naif0012_path:", naif0012_path) + + # --- Load global kernels --- + pyswice.furnsh_c(str(de430_path)) + pyswice.furnsh_c(str(naif0012_path)) + + # --- Resolve ephemeris path --- + eph = Path(ephemerisPath) + + if eph.is_dir(): + # historical Basilisk behavior: + # when passed a directory, only load pck00010.tpc + candidate = eph / "pck00010.tpc" + if not candidate.exists(): + raise RuntimeError( + f"Directory {eph} does not contain pck00010.tpc " + "(required for old-style planetPositionVelocity)" + ) + eph = candidate + + # Load ephemeris file + pyswice.furnsh_c(str(eph)) positionVelocity = spkRead(planetName, time, frame, observer) position = positionVelocity[0:3] * 1000 velocity = positionVelocity[3:6] * 1000 - pyswice.unload_c(bskPath + ephemerisPath) - return position, velocity # [m], [m/s] + # Unload only the ephemeris file we loaded + pyswice.unload_c(str(eph)) + + return position, velocity diff --git a/src/utilities/simIncludeGravBody.py b/src/utilities/simIncludeGravBody.py index 3c48125939..98bae785b9 100644 --- a/src/utilities/simIncludeGravBody.py +++ b/src/utilities/simIncludeGravBody.py @@ -14,6 +14,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +from pathlib import Path from typing import Optional from typing import Dict from typing import Iterable @@ -40,12 +41,15 @@ from Basilisk.utilities import unitTestSupport from Basilisk.utilities.deprecated import deprecationWarn +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile, POOCH # this statement is needed to enable Windows to print ANSI codes in the Terminal # see https://stackoverflow.com/questions/287871/how-to-print-colored-text-in-terminal-in-python/3332860#3332860 import os + os.system("") + @dataclass class BodyData: """A class that contains information about a body in the simulation. @@ -78,8 +82,8 @@ class BodyData: planetName="sun_planet_data", displayName="sun", modelDictionaryKey="", - mu=astroConstants.MU_SUN*1e9, - radEquator=astroConstants.REQ_SUN*1e3, + mu=astroConstants.MU_SUN * 1e9, + radEquator=astroConstants.REQ_SUN * 1e3, spicePlanetFrame="IAU_sun", ), "mercury": BodyData( @@ -87,8 +91,8 @@ class BodyData: planetName="mercury_planet_data", displayName="mercury", modelDictionaryKey="", - mu=astroConstants.MU_MERCURY*1e9, - radEquator=astroConstants.REQ_MERCURY*1e3, + mu=astroConstants.MU_MERCURY * 1e9, + radEquator=astroConstants.REQ_MERCURY * 1e3, spicePlanetFrame="IAU_mercury", ), "venus": BodyData( @@ -96,8 +100,8 @@ class BodyData: planetName="venus_planet_data", displayName="venus", modelDictionaryKey="", - mu=astroConstants.MU_VENUS*1e9, - radEquator=astroConstants.REQ_VENUS*1e3, + mu=astroConstants.MU_VENUS * 1e9, + radEquator=astroConstants.REQ_VENUS * 1e3, spicePlanetFrame="IAU_venus", ), "earth": BodyData( @@ -105,8 +109,8 @@ class BodyData: planetName="earth_planet_data", displayName="earth", modelDictionaryKey="", - mu=astroConstants.MU_EARTH*1e9, - radEquator=astroConstants.REQ_EARTH*1e3, + mu=astroConstants.MU_EARTH * 1e9, + radEquator=astroConstants.REQ_EARTH * 1e3, spicePlanetFrame="IAU_earth", ), "moon": BodyData( @@ -114,8 +118,8 @@ class BodyData: planetName="moon_planet_data", displayName="moon", modelDictionaryKey="", - mu=astroConstants.MU_MOON*1e9, - radEquator=astroConstants.REQ_MOON*1e3, + mu=astroConstants.MU_MOON * 1e9, + radEquator=astroConstants.REQ_MOON * 1e3, spicePlanetFrame="IAU_moon", ), "mars": BodyData( @@ -123,8 +127,8 @@ class BodyData: planetName="mars_planet_data", displayName="mars", modelDictionaryKey="", - mu=astroConstants.MU_MARS*1e9, - radEquator=astroConstants.REQ_MARS*1e3, + mu=astroConstants.MU_MARS * 1e9, + radEquator=astroConstants.REQ_MARS * 1e3, spicePlanetFrame="IAU_mars", ), "mars barycenter": BodyData( @@ -132,8 +136,8 @@ class BodyData: planetName="mars barycenter_planet_data", displayName="mars barycenter", modelDictionaryKey="", - mu=astroConstants.MU_MARS*1e9, - radEquator=astroConstants.REQ_MARS*1e3, + mu=astroConstants.MU_MARS * 1e9, + radEquator=astroConstants.REQ_MARS * 1e3, spicePlanetFrame="IAU_mars", ), "jupiter barycenter": BodyData( @@ -141,8 +145,8 @@ class BodyData: planetName="jupiter barycenter_planet_data", displayName="jupiter", modelDictionaryKey="", - mu=astroConstants.MU_JUPITER*1e9, - radEquator=astroConstants.REQ_JUPITER*1e3, + mu=astroConstants.MU_JUPITER * 1e9, + radEquator=astroConstants.REQ_JUPITER * 1e3, spicePlanetFrame="IAU_jupiter", ), "saturn": BodyData( @@ -150,8 +154,8 @@ class BodyData: planetName="saturn barycenter_planet_data", displayName="saturn", modelDictionaryKey="", - mu=astroConstants.MU_SATURN*1e9, - radEquator=astroConstants.REQ_SATURN*1e3, + mu=astroConstants.MU_SATURN * 1e9, + radEquator=astroConstants.REQ_SATURN * 1e3, spicePlanetFrame="IAU_saturn", ), "uranus": BodyData( @@ -159,8 +163,8 @@ class BodyData: planetName="uranus barycenter_planet_data", displayName="uranus", modelDictionaryKey="", - mu=astroConstants.MU_URANUS*1e9, - radEquator=astroConstants.REQ_URANUS*1e3, + mu=astroConstants.MU_URANUS * 1e9, + radEquator=astroConstants.REQ_URANUS * 1e3, spicePlanetFrame="IAU_uranus", ), "neptune": BodyData( @@ -168,8 +172,8 @@ class BodyData: planetName="neptune barycenter_planet_data", displayName="neptune", modelDictionaryKey="", - mu=astroConstants.MU_NEPTUNE*1e9, - radEquator=astroConstants.REQ_NEPTUNE*1e3, + mu=astroConstants.MU_NEPTUNE * 1e9, + radEquator=astroConstants.REQ_NEPTUNE * 1e3, spicePlanetFrame="IAU_neptune", ), } @@ -180,6 +184,11 @@ class WithGravField(Protocol): gravField: Any # cannot be GravityEffector because SWIG classes dont give type info +def _ensure_trailing_sep(path: str) -> str: + path = str(path) + return path if path.endswith(os.sep) else path + os.sep + + class gravBodyFactory: """Class to create gravitational bodies.""" @@ -348,12 +357,12 @@ def createSpiceInterface( self, path: str, time: str, - spiceKernelFileNames: Iterable[str] = [ - "de430.bsp", - "naif0012.tls", - "de-403-masses.tpc", - "pck00010.tpc", - ], + spiceKernelFileNames: Iterable[DataFile.EphemerisData] = ( + DataFile.EphemerisData.de430, + DataFile.EphemerisData.naif0012, + DataFile.EphemerisData.de_403_masses, + DataFile.EphemerisData.pck00010, + ), spicePlanetNames: Optional[Sequence[str]] = None, spicePlanetFrames: Optional[Sequence[str]] = None, epochInMsg: bool = False, @@ -389,12 +398,12 @@ def createSpiceInterface( *, path: str = "%BSK_PATH%/supportData/EphemerisData/", time: str, - spiceKernelFileNames: Iterable[str] = [ - "de430.bsp", - "naif0012.tls", - "de-403-masses.tpc", - "pck00010.tpc", - ], + spiceKernelFileNames: Iterable[DataFile.EphemerisData] = ( + DataFile.EphemerisData.de430, + DataFile.EphemerisData.naif0012, + DataFile.EphemerisData.de_403_masses, + DataFile.EphemerisData.pck00010, + ), spicePlanetNames: Optional[Sequence[str]] = None, spicePlanetFrames: Optional[Sequence[str]] = None, epochInMsg: bool = False, @@ -432,16 +441,16 @@ def createSpiceInterface( self, path: str = "%BSK_PATH%/supportData/EphemerisData/", time: str = "", - spiceKernelFileNames: Iterable[str] = [ - "de430.bsp", - "naif0012.tls", - "de-403-masses.tpc", - "pck00010.tpc", - ], + spiceKernelFileNames: Iterable[DataFile.EphemerisData] = ( + DataFile.EphemerisData.de430, + DataFile.EphemerisData.naif0012, + DataFile.EphemerisData.de_403_masses, + DataFile.EphemerisData.pck00010, + ), spicePlanetNames: Optional[Sequence[str]] = None, spicePlanetFrames: Optional[Sequence[str]] = None, epochInMsg: bool = False, - spiceKernalFileNames = None, + spiceKernalFileNames=None, ) -> spiceInterface.SpiceInterface: if time == "": raise ValueError( @@ -453,45 +462,72 @@ def createSpiceInterface( deprecationWarn( f"{gravBodyFactory.createSpiceInterface.__qualname__}.spiceKernalFileNames" "2024/11/24", - "The argument 'spiceKernalFileNames' is deprecated, as it is a " - "misspelling of 'spiceKernelFileNames'" + "The argument 'spiceKernalFileNames' is deprecated. Use 'spiceKernelFileNames'", ) - path = path.replace("%BSK_PATH%", list(__path__)[0]) + if isinstance(path, str) and "%BSK_PATH%" in path: + path = path.replace("%BSK_PATH%", list(__path__)[0]) + + path = Path(path).expanduser().resolve() + self.spiceObject = spiceInterface.SpiceInterface() + self.spiceObject.ModelTag = "SpiceInterfaceData" + self.spiceObject.SPICEDataPath = ( + str(Path(POOCH.abspath) / "supportData" / "EphemerisData") + os.sep + ) self.spiceKernelFileNames.extend(spiceKernelFileNames) self.spicePlanetNames = list(spicePlanetNames or self.gravBodies) if spicePlanetFrames is not None: self.spicePlanetFrames = list(spicePlanetFrames) - self.spiceObject = spiceInterface.SpiceInterface() - self.spiceObject.ModelTag = "SpiceInterfaceData" - self.spiceObject.SPICEDataPath = path self.spiceObject.addPlanetNames(self.spicePlanetNames) self.spiceObject.UTCCalInit = time if len(self.spicePlanetFrames) > 0: self.spiceObject.planetFrames = list(self.spicePlanetFrames) self.spiceObject.SPICELoaded = True - for fileName in set(self.spiceKernelFileNames): - if self.spiceObject.loadSpiceKernel(fileName, path): - # error occured loading spice kernel + for file_enum in set(self.spiceKernelFileNames): + # Resolve the file path using pooch if possible + try: + resolved_path = Path(get_path(file_enum)).resolve() + except Exception: + # Fallback: use the kernel filename directly inside SPICEDataPath + fname = getattr(file_enum, "value", str(file_enum)) + resolved_path = (Path(self.spiceObject.SPICEDataPath) / fname).resolve() + + # Absolute filename SPICE should load + full_kernel_path = str(resolved_path) + + # Optional debug print + print("=== Loading SPICE Kernel ===") + print("Resolved:", full_kernel_path) + + # ALWAYS load using the FULL path. Never split dir+filename. + resolved_dir = str(resolved_path.parent) + os.sep + resolved_name = resolved_path.name + print("=== DEBUG loadSpiceKernel call ===") + print("resolved_path:", resolved_path) + print("full_kernel_path:", full_kernel_path) + print("filename passed:", resolved_path.name) + print("directory passed:", str(resolved_path.parent) + os.sep) + print("type(filename):", type(resolved_path.name)) + print("type(directory):", type(str(resolved_path.parent) + os.sep)) + load_failed = self.spiceObject.loadSpiceKernel(resolved_name, resolved_dir) + print("loadSpiceKernel returned:", load_failed) + + if load_failed: self.spiceObject.SPICELoaded = False - if fileName == "de430.bsp": - print("\033[91mERROR loading the large file de430.bsp:\033[0m If BSK was " - "installed via a wheel, try running bskLargeData from the console to " - "install the large BSK data files.\n") + print(f"\033[91mERROR loading kernel:\033[0m {full_kernel_path}") - # subscribe Grav Body data to the spice state message + # Hook grav bodies to the SPICE messages for c, gravBodyDataItem in enumerate(self.gravBodies.values()): gravBodyDataItem.planetBodyInMsg.subscribeTo( self.spiceObject.planetStateOutMsgs[c] ) - # create and connect to an epoch input message if epochInMsg: self.epochMsg = unitTestSupport.timeStringToGregorianUTCMsg( - time, dataPath=path + time, dataPath=str(path) ) self.spiceObject.epochInMsg.subscribeTo(self.epochMsg) @@ -501,8 +537,12 @@ def unloadSpiceKernels(self): """Method to unload spice kernals at the end of a simulation.""" if self.spiceObject is None: return - for fileName in set(self.spiceKernelFileNames): - self.spiceObject.unloadSpiceKernel(fileName, self.spiceObject.SPICEDataPath) + for file_enum in set(self.spiceKernelFileNames): + # Convert enum → string filename + fname = getattr(file_enum, "value", str(file_enum)) + + d = _ensure_trailing_sep(self.spiceObject.SPICEDataPath) + self.spiceObject.unloadSpiceKernel(fname, d) def loadGravFromFile( diff --git a/src/utilities/supportDataTools/dataFetcher.py b/src/utilities/supportDataTools/dataFetcher.py index 2ec30d81e8..454404bf9a 100644 --- a/src/utilities/supportDataTools/dataFetcher.py +++ b/src/utilities/supportDataTools/dataFetcher.py @@ -25,6 +25,17 @@ from Basilisk.utilities.supportDataTools.registrySnippet import REGISTRY from Basilisk import __version__ +# Override URLs for large NAIF kernels (not in GitHub repo) +EXTERNAL_KERNEL_URLS = { + "supportData/EphemerisData/de430.bsp": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de430.bsp", + "supportData/EphemerisData/naif0012.tls": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/lsk/naif0012.tls", + "supportData/EphemerisData/pck00010.tpc": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/pck00010.tpc", + "supportData/EphemerisData/de-403-masses.tpc": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/de-403-masses.tpc", + "supportData/EphemerisData/hst_edited.bsp": "https://naif.jpl.nasa.gov/pub/naif/HST/kernels/spk/hst_edited.bsp", + "supportData/EphemerisData/nh_pred_od077.bsp": "https://naif.jpl.nasa.gov/pub/naif/pds/data/nh-j_p_ss-spice-6-v1.0/nhsp_1000/data/spk/nh_pred_od077.bsp", +} + + DATA_VERSION = f"v{__version__}" ALBEDO_DATA_BASE_PATH = "supportData/AlbedoData/" @@ -75,6 +86,7 @@ def find_local_support_data() -> Path | None: path=pooch.os_cache("bsk_support_data"), base_url=BASE_URL, registry=REGISTRY, + urls=EXTERNAL_KERNEL_URLS, ) diff --git a/src/utilities/supportDataTools/registrySnippet.py b/src/utilities/supportDataTools/registrySnippet.py new file mode 100644 index 0000000000..7757147d8a --- /dev/null +++ b/src/utilities/supportDataTools/registrySnippet.py @@ -0,0 +1,54 @@ +# Auto-generated registry of support data files by make_registry.py +# DO NOT EDIT THIS FILE BY HAND. Run makeRegistry.py to update. + +REGISTRY = { + "supportData/AlbedoData/Earth_ALB_2018_CERES_All_10x10.csv": "md5:e28102c2adaa281be7aaded85f4a7b4a", + "supportData/AlbedoData/Earth_ALB_2018_CERES_All_1x1.csv": "md5:5f8d5fa2bca05f9fb90d6206c7462e66", + "supportData/AlbedoData/Earth_ALB_2018_CERES_All_5x5.csv": "md5:b9ba41fe95642c425cd7b1e6e77f27d9", + "supportData/AlbedoData/Earth_ALB_2018_CERES_Clear_10x10.csv": "md5:559c40494e0c2ff21117b0d2b4cb4337", + "supportData/AlbedoData/Earth_ALB_2018_CERES_Clear_1x1.csv": "md5:aab189dcb8bdf847d20817a1ccf3bbbd", + "supportData/AlbedoData/Earth_ALB_2018_CERES_Clear_5x5.csv": "md5:a9190d8a2f9650f1e9ec3ba931acd1dd", + "supportData/AlbedoData/Mars_ALB_TES_10x10.csv": "md5:087522357956a61fc4cb960b7ba9357d", + "supportData/AlbedoData/Mars_ALB_TES_1x1.csv": "md5:e140359d0211b9a59c9f88e8a1c9827a", + "supportData/AlbedoData/Mars_ALB_TES_5x5.csv": "md5:614c068da2f194656cc8952fcfd989b3", + "supportData/AlbedoData/earthReflectivityMean_10x10.dat": "md5:55e4c55eaba239ba1e041a77b266d30a", + "supportData/AlbedoData/earthReflectivityMean_1p25x1.dat": "md5:af8a8e46b849606707534d9b8906c519", + "supportData/AlbedoData/earthReflectivityMean_5x5.dat": "md5:327e571b41d1e99198b4cd6d856e8b5a", + "supportData/AlbedoData/earthReflectivityStd_10x10.dat": "md5:c0e0cfd18597f508c093fda1bb76bbad", + "supportData/AlbedoData/earthReflectivityStd_1p25x1.dat": "md5:03208907ed87bbc27906f491a79ec8b1", + "supportData/AlbedoData/earthReflectivityStd_5x5.dat": "md5:b592e99b2edefdb1fa2f9e07ff7007ae", + "supportData/AlbedoData/marsReflectivityMean_10x10.dat": "md5:b3c9e1e7eec6372430b90708bc10a30c", + "supportData/AlbedoData/marsReflectivityMean_1p25x1.dat": "md5:ab382ea79e282bf63a2a80763854d093", + "supportData/AlbedoData/marsReflectivityMean_5x5.dat": "md5:d9b355a9ee2de3ad3f0b3316d742d325", + "supportData/AtmosphereData/EarthGRAMNominal.txt": "md5:f96ae0632e8253225770e1b927214675", + "supportData/AtmosphereData/JupiterGRAMNominal.csv": "md5:09e1283af38e292465024306619758eb", + "supportData/AtmosphereData/MarsGRAMNominal.txt": "md5:2c1e990dde154a5dd73c1a976f2f5b10", + "supportData/AtmosphereData/NRLMSISE00Nominal.txt": "md5:cf86856b06038a725c41a07175092158", + "supportData/AtmosphereData/NeptuneGRAMNominal.csv": "md5:5c500071dd9e61e862503ed454f808f0", + "supportData/AtmosphereData/TitanGRAMNominal.csv": "md5:0b9570ac5d3d178545deb0fd7311dcfa", + "supportData/AtmosphereData/USStandardAtmosphere1976.csv": "md5:891d4c45c6116415ca9dd153eac31bba", + "supportData/AtmosphereData/UranusGRAMNominal.csv": "md5:fb7ce2f93f80fb616318378491d7acba", + "supportData/AtmosphereData/VenusGRAMNominal.csv": "md5:d17ae5d6a30f510dc82554a6002f6dbb", + "supportData/DentonGEO/model_e_array_all.txt": "md5:dcd84e90630a26affe53c9f96b36e22b", + "supportData/DentonGEO/model_e_array_high.txt": "md5:93dbe65e6b0c20be7de3c8dbae7a4d79", + "supportData/DentonGEO/model_e_array_low.txt": "md5:a107047ac9675ac5bef46d792f8db707", + "supportData/DentonGEO/model_e_array_mid.txt": "md5:f66d014d156bcf2f16016bff72afff8c", + "supportData/DentonGEO/model_i_array_all.txt": "md5:049570c8c3fe6dca307d77dc72dcf94c", + "supportData/DentonGEO/model_i_array_high.txt": "md5:3695ac88ce113eb89c32e689ab0b7cc8", + "supportData/DentonGEO/model_i_array_low.txt": "md5:d282c5eb43efa2bdb50fd4e9ca5e4bc8", + "supportData/DentonGEO/model_i_array_mid.txt": "md5:b89c93a47bdd9f3884795992cc9a6e3c", + "supportData/EphemerisData/MVN_SCLKSCET.00000.tsc": "md5:8df85d39cff0440cd1a48115566145b4", + "supportData/EphemerisData/de-403-masses.tpc": "md5:00137bda9537bb47cbd076b6716f4267", + "supportData/EphemerisData/de430.bsp": "md5:91e21181f6a96edbfeb1ff5a419ce095", + "supportData/EphemerisData/hst_edited.bsp": "md5:f640e9baefb9492f17563a409aa3513c", + "supportData/EphemerisData/naif0011.tls": "md5:2a3a51a665e21f2f151527dace5ede74", + "supportData/EphemerisData/naif0012.tls": "md5:25a2fff30b0dedb4d76c06727b1895b1", + "supportData/EphemerisData/nh_pred_od077.bsp": "md5:68364f1862abd6cbc823f15fdb4e7079", + "supportData/EphemerisData/pck00010.tpc": "md5:da153641f7346bd5b6a1226778e0d51b", + "supportData/LocalGravData/GGM03S-J2-only.txt": "md5:08e8b083c473e8f5261628e1531a38e8", + "supportData/LocalGravData/GGM03S.txt": "md5:295d419493ad83f85c3ab2bcb8c37e7c", + "supportData/LocalGravData/GGM2BData.txt": "md5:79a4c3aa3199a68a136d25b7167a0cc1", + "supportData/LocalGravData/VESTA20H.txt": "md5:f9ec4308ea7049656f64e64d6019d570", + "supportData/LocalGravData/eros007790.tab": "md5:5bdf62cee6c7069ea547cca6ed7c724f", + "supportData/MagneticField/WMM.COF": "md5:8caac788545d3a32d55faba8aa87c93b", +} diff --git a/src/utilities/tests/test_ck_utilities.py b/src/utilities/tests/test_ck_utilities.py index 5dfde68341..8d370801eb 100644 --- a/src/utilities/tests/test_ck_utilities.py +++ b/src/utilities/tests/test_ck_utilities.py @@ -19,8 +19,18 @@ import pytest from Basilisk.simulation import spacecraft -from Basilisk.utilities import SimulationBaseClass, macros, pyswice_ck_utilities, simIncludeGravBody, RigidBodyKinematics as rbk +from Basilisk.utilities import ( + SimulationBaseClass, + macros, + pyswice_ck_utilities, + simIncludeGravBody, + RigidBodyKinematics as rbk, +) from Basilisk.topLevelModules import pyswice +from Basilisk.utilities.supportDataTools.dataFetcher import ( + get_path, + DataFile, +) @pytest.mark.timeout(30) # seconds @@ -37,25 +47,27 @@ def test_ck_read_write(tmp_path, show_plots): simulation.AddModelToTask(taskName, scObject) scObject.hub.mHub = 750.0 - scObject.hub.IHubPntBc_B = np.array([[900., 0., 0.], - [0., 800., 0.], - [0., 0., 600.]]) + scObject.hub.IHubPntBc_B = np.array( + [[900.0, 0.0, 0.0], [0.0, 800.0, 0.0], [0.0, 0.0, 600.0]] + ) scObject.hub.sigma_BNInit = [[0.1], [-0.2], [0.3]] scObject.hub.omega_BN_BInit = [[0.01], [-0.01], [0.03]] # Load up the leap second and spacecraft SPICE kernels - gravFactory = simIncludeGravBody.gravBodyFactory() - timeInit = 'FEB 01, 2021 12:00:00 (UTC)' - spiceObject = gravFactory.createSpiceInterface(time=timeInit) - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'naif0011.tls') # leap second file - pyswice.furnsh_c(spiceObject.SPICEDataPath + 'MVN_SCLKSCET.00000.tsc') # spacecraft clock file + timeInit = "FEB 01, 2021 12:00:00 (UTC)" + naif0011_path = get_path(DataFile.EphemerisData.naif0011) + mvn_sclk_path = get_path(DataFile.EphemerisData.MVN_SCLKSCET_00000) + pyswice.furnsh_c(str(naif0011_path)) + pyswice.furnsh_c(str(mvn_sclk_path)) scObjectLogger = scObject.scStateOutMsg.recorder(dynTaskRate) simulation.AddModelToTask(taskName, scObjectLogger) simulation.InitializeSimulation() - simulation.ConfigureStopTime(macros.sec2nano(59)) # run for 59 seconds for easy time logic + simulation.ConfigureStopTime( + macros.sec2nano(59) + ) # run for 59 seconds for easy time logic simulation.ExecuteSimulation() # Write a CK file using the attitude data from the simulation @@ -66,7 +78,9 @@ def test_ck_read_write(tmp_path, show_plots): fileNameStr = str(file_name) print(fileNameStr, flush=True) print("DEBUG: Before ckWrite", flush=True) - pyswice_ck_utilities.ckWrite(fileNameStr, timeWrite, sigmaWrite, omegaWrite, timeInit, spacecraft_id=-202) + pyswice_ck_utilities.ckWrite( + fileNameStr, timeWrite, sigmaWrite, omegaWrite, timeInit, spacecraft_id=-202 + ) # Read the same CK file to check if the values are identical print("DEBUG: Before ckInitialize", flush=True) @@ -74,12 +88,22 @@ def test_ck_read_write(tmp_path, show_plots): sigmaRead = np.empty_like(sigmaWrite) omegaRead = np.empty_like(omegaWrite) for idx in range(len(timeWrite)): - print(f"DEBUG: Entering loop iteration {idx}", flush=True) # Add this to see if it hangs within the loop + print( + f"DEBUG: Entering loop iteration {idx}", flush=True + ) # Add this to see if it hangs within the loop # Change the time string to account for increasing time - timeString = timeInit[:19] + f"{int(timeWrite[idx] * macros.NANO2SEC):02}" + timeInit[21:] - _, kernQuat, kernOmega = pyswice_ck_utilities.ckRead(timeString, spacecraft_id=-202) - - sigmaRead[idx, :] = - rbk.EP2MRP(kernQuat) # Convert from JPL-style quaternion notation + timeString = ( + timeInit[:19] + + f"{int(timeWrite[idx] * macros.NANO2SEC):02}" + + timeInit[21:] + ) + _, kernQuat, kernOmega = pyswice_ck_utilities.ckRead( + timeString, spacecraft_id=-202 + ) + + sigmaRead[idx, :] = -rbk.EP2MRP( + kernQuat + ) # Convert from JPL-style quaternion notation omegaRead[idx, :] = kernOmega print("DEBUG: Before ckClose", flush=True) pyswice_ck_utilities.ckClose(fileNameStr) diff --git a/src/utilities/unitTestSupport.py b/src/utilities/unitTestSupport.py index d96e0c7c94..efa91331a1 100755 --- a/src/utilities/unitTestSupport.py +++ b/src/utilities/unitTestSupport.py @@ -26,16 +26,18 @@ from Basilisk.architecture import bskUtilities from Basilisk.architecture import messaging from Basilisk.topLevelModules import pyswice +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + mpl.rc("figure", facecolor="white") -mpl.rc('xtick', labelsize=9) -mpl.rc('ytick', labelsize=9) +mpl.rc("xtick", labelsize=9) +mpl.rc("ytick", labelsize=9) mpl.rc("figure", figsize=(5.75, 2.5)) -mpl.rc('axes', labelsize=10) -mpl.rc('legend', fontsize=9) -mpl.rc('figure', autolayout=True) -mpl.rc('figure', max_open_warning=30) -mpl.rc('legend', loc='lower right') +mpl.rc("axes", labelsize=10) +mpl.rc("legend", fontsize=9) +mpl.rc("figure", autolayout=True) +mpl.rc("figure", max_open_warning=30) +mpl.rc("legend", loc="lower right") import matplotlib.colors as colors import matplotlib.cm as cmx @@ -57,18 +59,20 @@ # del(T.LATEX_ESCAPE_RULES['}']) # ''' - del (T.LATEX_ESCAPE_RULES[u'$']) - del (T.LATEX_ESCAPE_RULES[u'\\']) - del (T.LATEX_ESCAPE_RULES[u'_']) - del (T.LATEX_ESCAPE_RULES[u'{']) - del (T.LATEX_ESCAPE_RULES[u'}']) + del T.LATEX_ESCAPE_RULES["$"] + del T.LATEX_ESCAPE_RULES["\\"] + del T.LATEX_ESCAPE_RULES["_"] + del T.LATEX_ESCAPE_RULES["{"] + del T.LATEX_ESCAPE_RULES["}"] from Basilisk.utilities.tabulate import * except: pass + def isVectorEqual(result, truth, accuracy): """function to check if a 3D vector is the same as the truth values""" - if foundNAN(result): return 0 + if foundNAN(result): + return 0 if np.linalg.norm(result - truth) > accuracy: return 0 # return 0 to indicate the array's are not equal @@ -91,7 +95,8 @@ def isArrayEqual(result, truth, dim, accuracy): print("Truth array was empty") return 0 - if foundNAN(result): return 0 + if foundNAN(result): + return 0 for i in range(0, dim): if math.fabs(result[i] - truth[i]) > accuracy: @@ -115,7 +120,8 @@ def isArrayEqualRelative(result, truth, dim, accuracy): print("Truth array was empty") return 0 - if foundNAN(result): return 0 + if foundNAN(result): + return 0 for i in range(0, dim): if truth[i] == 0: @@ -140,18 +146,21 @@ def isArrayZero(result, dim, accuracy): print("Result array was empty") return 0 - if foundNAN(result): return 0 + if foundNAN(result): + return 0 for i in range(0, dim): - if (math.fabs(result[i]) > accuracy): + if math.fabs(result[i]) > accuracy: return 0 # return 0 to indicate the array's are not equal return 1 # return 1 to indicate the two array's are equal -def compareVector(trueStates, dataStates, accuracy, msg, testFailCount, testMessages, ExpectedResult=1): +def compareVector( + trueStates, dataStates, accuracy, msg, testFailCount, testMessages, ExpectedResult=1 +): """Compare two vector size and values and check absolute accuracy""" - if (len(trueStates) != len(dataStates)): + if len(trueStates) != len(dataStates): testFailCount += 1 testMessages.append("FAILED: " + msg + r" unequal data array sizes\n") else: @@ -178,7 +187,9 @@ def compareArray(trueStates, dataStates, accuracy, msg, testFailCount, testMessa return testFailCount, testMessages -def compareArrayND(trueStates, dataStates, accuracy, msg, size, testFailCount, testMessages): +def compareArrayND( + trueStates, dataStates, accuracy, msg, size, testFailCount, testMessages +): """Compare two arrays of size N for size and values and check absolute accuracy""" if len(trueStates) != len(dataStates): testFailCount += 1 @@ -203,7 +214,9 @@ def compareArrayND(trueStates, dataStates, accuracy, msg, size, testFailCount, t return testFailCount, testMessages -def compareArrayRelative(trueStates, dataStates, accuracy, msg, testFailCount, testMessages): +def compareArrayRelative( + trueStates, dataStates, accuracy, msg, testFailCount, testMessages +): """ Checks whether the relative distance between elements of a pullMessageLogData-derived array and a truth array is below a provided accuracy, and return an error if not. @@ -217,7 +230,7 @@ def compareArrayRelative(trueStates, dataStates, accuracy, msg, testFailCount, t testMessages: """ - if (len(trueStates) != len(dataStates)): + if len(trueStates) != len(dataStates): testFailCount += 1 testMessages.append("FAILED: " + msg + r" unequal data array sizes\n") elif len(trueStates) == 0 or len(dataStates) == 0: @@ -228,13 +241,20 @@ def compareArrayRelative(trueStates, dataStates, accuracy, msg, testFailCount, t # check a vector values if not isArrayEqualRelative(dataStates[i], trueStates[i], 3, accuracy): testFailCount += 1 - testMessages.append("FAILED: " + msg + " at t=" + str(dataStates[i, 0] * macros.NANO2SEC) + r"sec\n") + testMessages.append( + "FAILED: " + + msg + + " at t=" + + str(dataStates[i, 0] * macros.NANO2SEC) + + r"sec\n" + ) return testFailCount, testMessages def isDoubleEqual(result, truth, accuracy): """function to check if a double equals a truth value""" - if foundNAN(result): return 0 + if foundNAN(result): + return 0 if math.fabs(result - truth) > accuracy: return 0 # return 0 to indicate the doubles are not equal @@ -244,9 +264,12 @@ def isDoubleEqual(result, truth, accuracy): def isDoubleEqualRelative(result, truth, accuracy): """function to check if a double equals a truth value with relative tolerance""" - if foundNAN(result): return 0 - if foundNAN(truth): return 0 - if foundNAN(accuracy): return 0 + if foundNAN(result): + return 0 + if foundNAN(truth): + return 0 + if foundNAN(accuracy): + return 0 if truth == 0: print("truth is zero, cannot compare") return 0 @@ -257,7 +280,9 @@ def isDoubleEqualRelative(result, truth, accuracy): return 1 # return 1 to indicate the doubles are equal -def compareDoubleArrayRelative(trueStates, dataStates, accuracy, msg, testFailCount, testMessages): +def compareDoubleArrayRelative( + trueStates, dataStates, accuracy, msg, testFailCount, testMessages +): """Compare two arrays of doubles for size and values and check relative accuracy""" if len(trueStates) != len(dataStates): testFailCount += 1 @@ -274,7 +299,9 @@ def compareDoubleArrayRelative(trueStates, dataStates, accuracy, msg, testFailCo return testFailCount, testMessages -def compareDoubleArray(trueStates, dataStates, accuracy, msg, testFailCount, testMessages): +def compareDoubleArray( + trueStates, dataStates, accuracy, msg, testFailCount, testMessages +): """Compare two arrays of doubles for size and values and check absolute accuracy""" if len(trueStates) != len(dataStates): testFailCount += 1 @@ -291,7 +318,9 @@ def compareDoubleArray(trueStates, dataStates, accuracy, msg, testFailCount, tes return testFailCount, testMessages -def compareListRelative(trueStates, dataStates, accuracy, msg, testFailCount, testMessages): +def compareListRelative( + trueStates, dataStates, accuracy, msg, testFailCount, testMessages +): """Compare two row lists of values and check relative accuracy""" if len(trueStates) != len(dataStates): testFailCount += 1 @@ -333,18 +362,14 @@ def writeTableLaTeX(tableName, tableHeaders, caption, array, path): if exc.errno != errno.EEXIST: raise with open(texFileName, "w") as texTable: - table = tabulate(array, - tableHeaders, - tablefmt="latex", - numalign="center" - ) - - texTable.write(r'\begin{table}[htbp]') - texTable.write(r'\caption{' + caption + '}') - texTable.write(r'\label{tbl:' + tableName + '}') - texTable.write(r'\centering') + table = tabulate(array, tableHeaders, tablefmt="latex", numalign="center") + + texTable.write(r"\begin{table}[htbp]") + texTable.write(r"\caption{" + caption + "}") + texTable.write(r"\label{tbl:" + tableName + "}") + texTable.write(r"\centering") texTable.write(table) - texTable.write(r'\end{table}') + texTable.write(r"\end{table}") texTable.close() return @@ -370,7 +395,16 @@ def writeTeXSnippet(snippetName, texSnippet, path): def saveScenarioFigure(figureName, plt, path, extension=".svg"): """save a python scenario result into the documentation image folder""" - imgFileName = os.path.join(path, "..", "..", "docs", "source", "_images", "Scenarios", figureName + extension) + imgFileName = os.path.join( + path, + "..", + "..", + "docs", + "source", + "_images", + "Scenarios", + figureName + extension, + ) if not os.path.exists(os.path.dirname(imgFileName)): try: os.makedirs(os.path.dirname(imgFileName)) @@ -394,7 +428,9 @@ def saveFigurePDF(figureName, plt, path): def writeFigureLaTeX(figureName, caption, plt, format, path): """Save a figure and associated TeX code snippet""" - texFileName = os.path.join(path, "..", "_Documentation", "AutoTeX", figureName + ".tex") + texFileName = os.path.join( + path, "..", "_Documentation", "AutoTeX", figureName + ".tex" + ) if not os.path.exists(os.path.dirname(texFileName)): try: os.makedirs(os.path.dirname(texFileName)) @@ -402,12 +438,14 @@ def writeFigureLaTeX(figureName, caption, plt, format, path): if exc.errno != errno.EEXIST: raise with open(texFileName, "w") as texFigure: - texFigure.write(r'\begin{figure}[htbp]') - texFigure.write(r'\centerline{') - texFigure.write(r'\includegraphics[' + format + ']{AutoTeX/' + figureName + r'}}') - texFigure.write(r'\caption{' + caption + r'}') - texFigure.write(r'\label{fig:' + figureName + r'}') - texFigure.write(r'\end{figure}') + texFigure.write(r"\begin{figure}[htbp]") + texFigure.write(r"\centerline{") + texFigure.write( + r"\includegraphics[" + format + "]{AutoTeX/" + figureName + r"}}" + ) + texFigure.write(r"\caption{" + caption + r"}") + texFigure.write(r"\label{fig:" + figureName + r"}") + texFigure.write(r"\end{figure}") texFigure.close() texFileName = path + "/../_Documentation/AutoTeX/" + figureName + ".pdf" @@ -418,7 +456,7 @@ def writeFigureLaTeX(figureName, caption, plt, format, path): def foundNAN(array): """check if an array contains NAN values""" - if (np.isnan(np.sum(array))): + if np.isnan(np.sum(array)): print("Warning: found NaN value.") return 1 # return 1 to indicate a NaN value was found return 0 @@ -427,7 +465,7 @@ def foundNAN(array): def getLineColor(idx, maxNum): """pick a nicer color pattern to plot 3 vector components""" values = list(range(0, maxNum + 2)) - colorMap = mpl.pyplot.get_cmap('gist_earth') + colorMap = mpl.pyplot.get_cmap("gist_earth") cNorm = colors.Normalize(vmin=0, vmax=values[-1]) scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=colorMap) return scalarMap.to_rgba(values[idx + 1]) @@ -436,9 +474,9 @@ def getLineColor(idx, maxNum): def np2EigenMatrix3d(mat): """convert 3D numpy matrix to Eigen matrix""" return [ - [mat[0], mat[1], mat[2]] - , [mat[3], mat[4], mat[5]] - , [mat[6], mat[7], mat[8]] + [mat[0], mat[1], mat[2]], + [mat[3], mat[4], mat[5]], + [mat[6], mat[7], mat[8]], ] @@ -450,6 +488,7 @@ def np2EigenVectorXd(vec): return npVec + def npList2EigenXdVector(list): """Conver a list of arrays to a list of eigen values""" eigenList = bskUtilities.Eigen3dVector() @@ -457,10 +496,12 @@ def npList2EigenXdVector(list): eigenList.push_back(pos) return eigenList + def EigenVector3d2np(eig): """convert Eigen vector3d to numpy""" return np.array([eig[0][0], eig[1][0], eig[2][0]]) + def flattenList(matrix): """ returns a flattened list @@ -475,12 +516,12 @@ def flattenList(matrix): flat_list.extend(row) return flat_list + def EigenVector3d2list(eig): """convert Eigen vector3d to list""" return EigenVector3d2np(eig).tolist() - def pullVectorSetFromData(inpMat): """extract the vector data set from a data matrix where the 1st column is the time information""" outMat = np.array(inpMat).transpose() @@ -498,31 +539,34 @@ def decimalYearToDateTime(start): rem = start - year base = datetime(year, 1, 1) - return base + timedelta(seconds=(base.replace(year=base.year + 1) - base).total_seconds() * rem) + return base + timedelta( + seconds=(base.replace(year=base.year + 1) - base).total_seconds() * rem + ) def timeStringToGregorianUTCMsg(DateSpice, **kwargs): """convert a general time/date string to a gregoarian UTC msg object""" # set the data path - if 'dataPath' in kwargs: - dataPath = kwargs['dataPath'] + if "dataPath" in kwargs: + dataPath = kwargs["dataPath"] if not isinstance(dataPath, str): - print('ERROR: dataPath must be a string argument') + print("ERROR: dataPath must be a string argument") exit(1) else: - dataPath = bskPath + '/supportData/EphemerisData/' # default value + dataPath = bskPath + "/supportData/EphemerisData/" # default value # load spice kernel and convert the string into a UTC date/time string - pyswice.furnsh_c(dataPath + 'naif0012.tls') + naif0012_path = get_path(DataFile.EphemerisData.naif0012) + pyswice.furnsh_c(str(naif0012_path)) et = pyswice.new_doubleArray(1) pyswice.str2et_c(DateSpice, et) etEpoch = pyswice.doubleArray_getitem(et, 0) - ep1 = pyswice.et2utc_c(etEpoch, 'C', 6, 255, "Yo") - pyswice.unload_c(dataPath + 'naif0012.tls') # leap second file + ep1 = pyswice.et2utc_c(etEpoch, "C", 6, 255, "Yo") + pyswice.unload_c(str(naif0012_path)) try: # convert UTC string to datetime object - datetime_object = datetime.strptime(ep1, '%Y %b %d %H:%M:%S.%f') + datetime_object = datetime.strptime(ep1, "%Y %b %d %H:%M:%S.%f") # Validate month is in range 1-12 if datetime_object.month < 1 or datetime_object.month > 12: @@ -535,12 +579,14 @@ def timeStringToGregorianUTCMsg(DateSpice, **kwargs): epochMsgStructure.day = datetime_object.day epochMsgStructure.hours = datetime_object.hour epochMsgStructure.minutes = datetime_object.minute - epochMsgStructure.seconds = datetime_object.second + datetime_object.microsecond / 1e6 + epochMsgStructure.seconds = ( + datetime_object.second + datetime_object.microsecond / 1e6 + ) epochMsg = messaging.EpochMsg().write(epochMsgStructure) # Store the message in a global registry to prevent garbage collection - if not hasattr(timeStringToGregorianUTCMsg, '_msg_registry'): + if not hasattr(timeStringToGregorianUTCMsg, "_msg_registry"): timeStringToGregorianUTCMsg._msg_registry = [] timeStringToGregorianUTCMsg._msg_registry.append(epochMsg) @@ -551,6 +597,7 @@ def timeStringToGregorianUTCMsg(DateSpice, **kwargs): print(f"Original input string was: {DateSpice}") raise + def columnToRowList(set): """Loop through a column list and return a row list""" ans = [] @@ -558,18 +605,23 @@ def columnToRowList(set): ans.append(item[0]) return ans + def checkMethodKeyword(karglist, kwargs): """loop through list of method keyword arguments and make sure that an approved keyword is used.""" for key in kwargs: if key not in karglist: - print('ERROR: you tried to use an incorrect keyword ' + key + '. Options include:') + print( + "ERROR: you tried to use an incorrect keyword " + + key + + ". Options include:" + ) print(karglist) exit(1) def removeTimeFromData(dataList): """pull out the time column out of a 4xN data list""" - return (dataList.transpose()[1:len(dataList[0])]).transpose() + return (dataList.transpose()[1 : len(dataList[0])]).transpose() def samplingTime(simTime, baseTimeStep, numDataPoints): From a68cb983d7ac10327ab980ef00c5d787e2aa01bc Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 14:12:27 -0700 Subject: [PATCH 06/15] Update DentonFluxModel to load data via configured file paths --- .../_UnitTest/test_dentonFluxModel.py | 16 ++++++--- .../dentonFluxModel/dentonFluxModel.cpp | 34 ++++++++++++------- .../dentonFluxModel/dentonFluxModel.h | 16 ++++----- .../dentonFluxModel/dentonFluxModel.i | 7 +++- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/simulation/environment/dentonFluxModel/_UnitTest/test_dentonFluxModel.py b/src/simulation/environment/dentonFluxModel/_UnitTest/test_dentonFluxModel.py index 08c6aa5968..130712399a 100644 --- a/src/simulation/environment/dentonFluxModel/_UnitTest/test_dentonFluxModel.py +++ b/src/simulation/environment/dentonFluxModel/_UnitTest/test_dentonFluxModel.py @@ -1,12 +1,12 @@ -# +# # ISC License -# +# # Copyright (c) 2021, 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 @@ -39,6 +39,8 @@ from Basilisk.architecture import messaging from Basilisk.utilities import macros from Basilisk.simulation import dentonFluxModel +from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + Kps = ['0o', '4-', '5+'] LTs = [0.00, 14.73] @@ -97,7 +99,11 @@ def dentonFluxModelTestFunction(show_plots, param1_Kp, param2_LT, param3_z, para module.ModelTag = "dentonFluxModule" module.kpIndex = param1_Kp module.numOutputEnergies = 6 - module.dataPath = bskPath + '/supportData/DentonGEO/' + e_path = get_path(DataFile.DentonGEOData.model_e_array_all) + i_path = get_path(DataFile.DentonGEOData.model_i_array_all) + + # configure DentonFluxModel with explicit file paths + module.configureDentonFiles(str(e_path), str(i_path)) unitTestSim.AddModelToTask(unitTaskName, module) diff --git a/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp b/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp index 763c90a6a3..a41d29e6ee 100644 --- a/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp +++ b/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp @@ -59,10 +59,6 @@ void DentonFluxModel::Reset(uint64_t CurrentSimNanos) { bskLogger.bskLog(BSK_ERROR, "DentonFluxModel.kpIndex was not set."); } - if (this->dataPath == "") - { - bskLogger.bskLog(BSK_ERROR, "DentonFluxModel.dataPath was not set."); - } // Check the desired array size is not larger than the maximum value if (this->numOutputEnergies > MAX_PLASMA_FLUX_SIZE) { @@ -119,12 +115,26 @@ void DentonFluxModel::Reset(uint64_t CurrentSimNanos) this->inputEnergies[i] = this->inputEnergies[i-1] + step; } - // Read in Denton data files - readDentonDataFile(this->eDataFileName, this->mean_e_flux); - readDentonDataFile(this->iDataFileName, this->mean_i_flux); + if (this->eDataFullPath == "" || this->iDataFullPath == "") { + bskLogger.bskLog(BSK_ERROR, "DentonFluxModel: configureDentonFiles() was not called."); + return; + } + + readDentonDataFile(this->eDataFullPath, this->mean_e_flux); + readDentonDataFile(this->iDataFullPath, this->mean_i_flux); } +/*! Method to configure the Denton data file paths + @param eFile electron data file full path + @param iFile ion data file full path +*/ +void DentonFluxModel::configureDentonFiles(const std::string& eFile, const std::string& iFile) +{ + this->eDataFullPath = eFile; + this->iDataFullPath = iFile; +} + /*! This is the main method that gets called every time the module is updated. Provide an appropriate description. @param CurrentSimNanos current simulation time in nano-seconds @@ -321,16 +331,13 @@ double DentonFluxModel::bilinear(int x1, int x2, double y1, double y2, double y, @param data data array pointer */ -void DentonFluxModel::readDentonDataFile(std::string fileName, +void DentonFluxModel::readDentonDataFile(const std::string& fullPath, double data[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]) { double temp = 0.0; - // Input file stream object - std::ifstream inputFile; - // Read data from file: - inputFile.open(this->dataPath + fileName); + std::ifstream inputFile(fullPath); // Read information into array: Data includes information about mean, standard deviation, // median and percentiles (7 types of values in total). Only mean is relevant for this module @@ -351,7 +358,8 @@ void DentonFluxModel::readDentonDataFile(std::string fileName, } } } else { - bskLogger.bskLog(BSK_ERROR, ("Could not open " + this->dataPath + fileName).c_str()); + bskLogger.bskLog(BSK_ERROR, ("Could not open " + fullPath).c_str()); + return; } // Close file diff --git a/src/simulation/environment/dentonFluxModel/dentonFluxModel.h b/src/simulation/environment/dentonFluxModel/dentonFluxModel.h index 741ac80aab..1acd0d8991 100644 --- a/src/simulation/environment/dentonFluxModel/dentonFluxModel.h +++ b/src/simulation/environment/dentonFluxModel/dentonFluxModel.h @@ -43,13 +43,13 @@ class DentonFluxModel: public SysModel { // Methods void Reset(uint64_t CurrentSimNanos) override; void UpdateState(uint64_t CurrentSimNanos) override; - + void configureDentonFiles(const std::string& eFile, const std::string& iFile); + /* public variables */ int numOutputEnergies = -1; //!< number of energy bins used in the output message std::string kpIndex = ""; //!< Kp index - std::string dataPath = ""; //!< -- String with the path to the Denton GEO data - std::string eDataFileName = "model_e_array_all.txt"; //!< file name of the electron data file - std::string iDataFileName = "model_i_array_all.txt"; //!< file name of the ion data file + std::string eDataFullPath; //! Full path to the electron flux model file selected by the user. + std::string iDataFullPath; //! Full path to the ion flux model file selected by the user. ReadFunctor scStateInMsg; //!< spacecraft state input message ReadFunctor earthStateInMsg; //!< Earth planet state input message @@ -62,7 +62,7 @@ class DentonFluxModel: public SysModel { private: void calcLocalTime(double v1[3], double v2[3]); double bilinear(int, int, double, double, double, double, double, double, double); - void readDentonDataFile(std::string fileName, double data[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]); + void readDentonDataFile(const std::string& fullPath, double data[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]); int kpIndexCounter; //!< Kp index counter (betweeen 0 and 27) double localTime; //!< spacecraft location time relative to sun heading at GEO @@ -72,10 +72,10 @@ class DentonFluxModel: public SysModel { //!< Electron Flux: double mean_e_flux[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]; - + //!< Ion Flux: double mean_i_flux[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]; - + //!< Fill average centre energies, normalized by satellite double enElec[40] = {1.034126, 1.346516, 1.817463, 2.399564, 3.161048, 4.153217, 5.539430, 7.464148, @@ -87,7 +87,7 @@ class DentonFluxModel: public SysModel { 2069.619628, 2703.301269, 3540.124511, 4639.775390, 6069.347656, 7957.457519, 10436.841796, 13677.195312, 17923.560546, 23488.560546, 30782.000000, 40326.937500}; - + double enProt[40] = { 1.816424, 2.284231, 2.904752, 3.639589, 4.483188, 5.671049, 7.343667, 9.450922, 11.934194, 15.105951, 19.372854, 24.943658, diff --git a/src/simulation/environment/dentonFluxModel/dentonFluxModel.i b/src/simulation/environment/dentonFluxModel/dentonFluxModel.i index 5aed9d94a4..766d47230d 100644 --- a/src/simulation/environment/dentonFluxModel/dentonFluxModel.i +++ b/src/simulation/environment/dentonFluxModel/dentonFluxModel.i @@ -19,6 +19,7 @@ %module dentonFluxModel +%include "std_string.i" %include "architecture/utilities/bskException.swg" %default_bsk_exception(); @@ -34,7 +35,11 @@ %include "sys_model.i" %include "dentonFluxModel.h" - +%extend DentonFluxModel { + void configureDentonFiles(std::string eFile, std::string iFile) { + $self->configureDentonFiles(eFile, iFile); + } +} %include "architecture/msgPayloadDefC/SCStatesMsgPayload.h" struct SCStatesMsg_C; %include "architecture/msgPayloadDefC/SpicePlanetStateMsgPayload.h" From 39b9e4c4c5277fc3e9ba0f62430a170826869262 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 14:14:42 -0700 Subject: [PATCH 07/15] Update Denton Flux Model docs to show configuration --- .../dentonFluxModel/dentonFluxModel.cpp | 5 ----- .../dentonFluxModel/dentonFluxModel.h | 16 ++++++++++++++-- .../dentonFluxModel/dentonFluxModel.rst | 15 +++++++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp b/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp index a41d29e6ee..951a41500e 100644 --- a/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp +++ b/src/simulation/environment/dentonFluxModel/dentonFluxModel.cpp @@ -326,11 +326,6 @@ double DentonFluxModel::bilinear(int x1, int x2, double y1, double y2, double y, } -/*! Read in the Denton data file - @param fileName data file name - @param data data array pointer - -*/ void DentonFluxModel::readDentonDataFile(const std::string& fullPath, double data[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]) { diff --git a/src/simulation/environment/dentonFluxModel/dentonFluxModel.h b/src/simulation/environment/dentonFluxModel/dentonFluxModel.h index 1acd0d8991..4d6b476b5c 100644 --- a/src/simulation/environment/dentonFluxModel/dentonFluxModel.h +++ b/src/simulation/environment/dentonFluxModel/dentonFluxModel.h @@ -48,8 +48,12 @@ class DentonFluxModel: public SysModel { /* public variables */ int numOutputEnergies = -1; //!< number of energy bins used in the output message std::string kpIndex = ""; //!< Kp index - std::string eDataFullPath; //! Full path to the electron flux model file selected by the user. - std::string iDataFullPath; //! Full path to the ion flux model file selected by the user. + + /// @brief Full path to the electron flux model file selected by the user. + std::string eDataFullPath; + + /// @brief Full path to the ion flux model file selected by the user. + std::string iDataFullPath; ReadFunctor scStateInMsg; //!< spacecraft state input message ReadFunctor earthStateInMsg; //!< Earth planet state input message @@ -62,6 +66,14 @@ class DentonFluxModel: public SysModel { private: void calcLocalTime(double v1[3], double v2[3]); double bilinear(int, int, double, double, double, double, double, double, double); + + /*! + * @brief Read in a Denton-format flux data file and load its contents. + * + * @param fullPath Full filesystem path to the Denton data file. + * @param data Output array that will be filled with flux values + * indexed as [Kp][energy][localTime]. + */ void readDentonDataFile(const std::string& fullPath, double data[MAX_NUM_KPS][MAX_NUM_ENERGIES][MAX_NUM_LOCAL_TIMES]); int kpIndexCounter; //!< Kp index counter (betweeen 0 and 27) diff --git a/src/simulation/environment/dentonFluxModel/dentonFluxModel.rst b/src/simulation/environment/dentonFluxModel/dentonFluxModel.rst index 8866679897..558cea9804 100644 --- a/src/simulation/environment/dentonFluxModel/dentonFluxModel.rst +++ b/src/simulation/environment/dentonFluxModel/dentonFluxModel.rst @@ -9,9 +9,9 @@ flux data. Message Connection Descriptions ------------------------------- -The following table lists all the module input and output messages. -The module msg connection is set by the user from python. -The msg type contains a link to the message structure definition, while the description +The following table lists all the module input and output messages. +The module msg connection is set by the user from python. +The msg type contains a link to the message structure definition, while the description provides information on what this message is used for. .. _ModuleIO_Denton_Flux_Model: @@ -63,11 +63,18 @@ The Denton model averaged GEO space plasma properties module is created using: .. code-block:: python :linenos: + from Basilisk.utilities.supportDataTools.dataFetcher import get_path, DataFile + fluxModule = dentonFluxModel.DentonFluxModel() fluxModule.ModelTag = "dentonFluxModule" - fluxModule.dataPath = bskPath + '/supportData/DentonGEO/' fluxModule.kpIndex = "2+" fluxModule.numOutputEnergies = 30 + + # Configuration of Denton mean flux data files + e_file = get_path(DataFile.DentonGEOData.model_e_array_all) + i_file = get_path(DataFile.DentonGEOData.model_i_array_all) + fluxModule.configureDentonFiles(str(e_file), str(i_file)) + scSim.AddModelToTask(dynTaskName, fluxModule) The :math:`K_p` index (``kpIndex``) and number of output energies (``numOutputEnergies``) must be From f3dd543768e8bebc1c254e0db62c9a7a41f99cc1 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 16:30:13 -0700 Subject: [PATCH 08/15] Remove supportData from wheels and sdist --- MANIFEST.in | 1 + pyproject.toml | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index e21cfb89a6..f00754ed84 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ prune docs prune src/tests prune **/_UnitTest prune **/_Documentation +prune supportData global-exclude *.pdf *.png *.jpg *.jpeg *.gif *.JPG *.svg *.psd global-exclude *.ipynb *.bib *.tex diff --git a/pyproject.toml b/pyproject.toml index f6001ed8ea..a1f5eba901 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,17 +41,6 @@ include-package-data = true [tool.setuptools.package-data] "*" = ["*.so", "*.dll", "*.lib", "*.pyd", "*.a", "*.dylib"] # Include all built objects. -Basilisk = [ - "supportData/AlbedoData/*", - "supportData/AtmosphereData/*", - "supportData/DentonGEO/*", - "supportData/EphemerisData/*.tpc", - "supportData/EphemerisData/*.dat", - "supportData/EphemerisData/*.tls", - "supportData/EphemerisData/*.tsc", - "supportData/LocalGravData/*", - "supportData/MagneticField/*" -] [tool.setuptools.dynamic] version = {file = "docs/source/bskVersion.txt"} From aee0d9e2c25f552a3cc1842cb726f4cb3c6d022b Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 16:30:28 -0700 Subject: [PATCH 09/15] Ignore hashes for external files from JPL --- src/utilities/supportDataTools/dataFetcher.py | 4 ++++ src/utilities/supportDataTools/registrySnippet.py | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utilities/supportDataTools/dataFetcher.py b/src/utilities/supportDataTools/dataFetcher.py index 454404bf9a..70303dec3d 100644 --- a/src/utilities/supportDataTools/dataFetcher.py +++ b/src/utilities/supportDataTools/dataFetcher.py @@ -35,6 +35,10 @@ "supportData/EphemerisData/nh_pred_od077.bsp": "https://naif.jpl.nasa.gov/pub/naif/pds/data/nh-j_p_ss-spice-6-v1.0/nhsp_1000/data/spk/nh_pred_od077.bsp", } +# Do not set hashes for files fetched from external URLs such as JPL NAIF as +# they may change without notice. +for key in EXTERNAL_KERNEL_URLS: + REGISTRY[key] = None DATA_VERSION = f"v{__version__}" diff --git a/src/utilities/supportDataTools/registrySnippet.py b/src/utilities/supportDataTools/registrySnippet.py index 7757147d8a..f70c021b51 100644 --- a/src/utilities/supportDataTools/registrySnippet.py +++ b/src/utilities/supportDataTools/registrySnippet.py @@ -39,11 +39,8 @@ "supportData/DentonGEO/model_i_array_mid.txt": "md5:b89c93a47bdd9f3884795992cc9a6e3c", "supportData/EphemerisData/MVN_SCLKSCET.00000.tsc": "md5:8df85d39cff0440cd1a48115566145b4", "supportData/EphemerisData/de-403-masses.tpc": "md5:00137bda9537bb47cbd076b6716f4267", - "supportData/EphemerisData/de430.bsp": "md5:91e21181f6a96edbfeb1ff5a419ce095", - "supportData/EphemerisData/hst_edited.bsp": "md5:f640e9baefb9492f17563a409aa3513c", "supportData/EphemerisData/naif0011.tls": "md5:2a3a51a665e21f2f151527dace5ede74", "supportData/EphemerisData/naif0012.tls": "md5:25a2fff30b0dedb4d76c06727b1895b1", - "supportData/EphemerisData/nh_pred_od077.bsp": "md5:68364f1862abd6cbc823f15fdb4e7079", "supportData/EphemerisData/pck00010.tpc": "md5:da153641f7346bd5b6a1226778e0d51b", "supportData/LocalGravData/GGM03S-J2-only.txt": "md5:08e8b083c473e8f5261628e1531a38e8", "supportData/LocalGravData/GGM03S.txt": "md5:295d419493ad83f85c3ab2bcb8c37e7c", From 8b44f7b6abef623bb58374bf0fbf693d185a2694 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 16:37:26 -0700 Subject: [PATCH 10/15] Updated bskLargeData to prefetch content --- src/utilities/bskLargeData.py | 134 ++++++++++++++-------------------- 1 file changed, 53 insertions(+), 81 deletions(-) diff --git a/src/utilities/bskLargeData.py b/src/utilities/bskLargeData.py index c16ad91d2e..11fd8c27e2 100644 --- a/src/utilities/bskLargeData.py +++ b/src/utilities/bskLargeData.py @@ -16,98 +16,70 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -import os +import logging import sys -import requests -import importlib.util -from tqdm import tqdm - -# define the print color codes -statusColor = '\033[92m' -endColor = '\033[0m' - -def download_file(url, destination_path): - """Download a file from a URL with a progress bar.""" - try: - print(f"Downloading file from {url}...") - response = requests.get(url, stream=True, timeout=10) - response.raise_for_status() # Raise an error for bad HTTP responses - - # Get the total file size from headers - total_size = int(response.headers.get("content-length", 0)) - - os.makedirs(os.path.dirname(destination_path), exist_ok=True) # Ensure destination folder exists - - # Download the file with tqdm progress bar - with open(destination_path, "wb") as file, tqdm( - desc=f"Downloading {os.path.basename(destination_path)}", - total=total_size, - unit="B", - unit_scale=True, - unit_divisor=1024, - ) as bar: - for chunk in response.iter_content(chunk_size=1024): - file.write(chunk) - bar.update(len(chunk)) - - print(f"File downloaded and saved to {destination_path}.") - except requests.exceptions.RequestException as e: - print(f"Error downloading file: {e}") - sys.exit(1) - except Exception as e: - print(f"Unexpected error: {e}") - sys.exit(1) -def main(): - """ - Large BSK data files are downloaded directly from their web server - and are then installed in the local Basilisk python package. - For example, if using a python 3.11 virtual environment, the Spice ``*.bsp`` files - will be stored in:: +import pooch +from tqdm import tqdm - .../.venv/lib/python3.11/site-packages/Basilisk/supportData/EphemerisData +from Basilisk.utilities.supportDataTools.dataFetcher import ( + POOCH, + LOCAL_SUPPORT, +) +from Basilisk.utilities.supportDataTools.registrySnippet import REGISTRY - This function is useful if Basilisk is installed via a wheel which does not contain - these large BSK data file to keep the wheel file size reasonable. Calling this - python file allows these files to be installed in an automated manner. +statusColor = "\033[92m" +endColor = "\033[0m" - If internet access is not available, these large BSK data files can be - included in the above python package installation directly as well. +def quiet_fetch(rel: str): """ + Silences Pooch logging for a single fetch call. + """ + pooch_logger = pooch.utils.get_logger() + old_level = pooch_logger.level + pooch_logger.setLevel(logging.CRITICAL) - # Display the task message - print(f"{statusColor}Task: Downloading large BSK data files{endColor}") - - # Step 1: Determine the file location of the Basilisk package try: - spec = importlib.util.find_spec("Basilisk") - if spec is None: - print("Basilisk package not found. Ensure it is installed.") - sys.exit(1) - basilisk_path = os.path.dirname(spec.origin) - except Exception as e: - print(f"Error locating Basilisk package: {e}") + return POOCH.fetch(rel) + finally: + pooch_logger.setLevel(old_level) + + +def main(): + print(f"{statusColor}Task: Pre-fetching Basilisk supportData files{endColor}") + + all_relpaths = sorted(REGISTRY.keys()) + failures = [] + successes = 0 + + for rel in tqdm(all_relpaths, desc="Fetching data files", unit="file"): + try: + if LOCAL_SUPPORT: + local = LOCAL_SUPPORT / rel + if local.exists(): + successes += 1 + continue + # Output progress + tqdm.write(f"Downloading {rel}") + quiet_fetch(rel) + successes += 1 + + except Exception as e: + failures.append((rel, str(e))) + + print(f"\n{statusColor}Completed pre-fetch.{endColor}") + print(f"Fetched {successes} / {len(all_relpaths)} files.") + + if failures: + print("\nSome files could not be downloaded:") + for rel, err in failures: + print(f" {rel}: {err}") sys.exit(1) - # Step 2: Define the download URLs and destination paths - files_to_download = { - "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de430.bsp": os.path.join( - basilisk_path, "supportData", "EphemerisData", "de430.bsp" - ), - "https://naif.jpl.nasa.gov/pub/naif/HST/kernels/spk/hst_edited.bsp": os.path.join( - basilisk_path, "supportData", "EphemerisData", "hst_edited.bsp" - ), - "https://naif.jpl.nasa.gov/pub/naif/pds/data/nh-j_p_ss-spice-6-v1.0/nhsp_1000/data/spk/nh_pred_od077.bsp": os.path.join( - basilisk_path, "supportData", "EphemerisData", "nh_pred_od077.bsp" - ), - } - - # Step 3: Download each file - for url, destination_path in files_to_download.items(): - download_file(url, destination_path) - - print("All files downloaded successfully.") + print("All supportData files fetched successfully!") + sys.exit(0) + if __name__ == "__main__": main() From 8de0b1c9e2c1b0e63f36aaff7507fb625d1dd66a Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 16:40:32 -0700 Subject: [PATCH 11/15] Prefetch support data when building docs CI --- .github/workflows/pull-request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a28557f0d1..bb772f768c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -126,5 +126,7 @@ jobs: with: python-version: 3.13 conan-args: --opNav True --allOptPkg --mujoco True --mujocoReplay True + - name: Pre-fetch supportData + run: bskLargeData - name: Build docs uses: ./.github/actions/docs From 68cecd147fbfc06162f1f96d04d234d2cee3ec49 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 16:52:44 -0700 Subject: [PATCH 12/15] Minor changes to bskLargeData docs --- docs/source/Install/pipInstall.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/Install/pipInstall.rst b/docs/source/Install/pipInstall.rst index 9496a7be84..2c2450ed72 100644 --- a/docs/source/Install/pipInstall.rst +++ b/docs/source/Install/pipInstall.rst @@ -66,7 +66,7 @@ The main benefit of this approach will come in the future, when a set of pre-com allowing most users to easily ``pip install Basilisk`` without compilation, in the same way that packages like ``numpy``, ``scipy``, and ``pandas`` are available. -To keep the wheel size smaller, the large BSK data files are not installed by default. If the user +To keep the wheel size smaller, the BSK data files are not installed by default. If the user wants to use script that assumes they are included into the Basilisk python package, then go to the command line, change the current directory to be inside the environment where Basilisk was ``pip`` installed, and run the command:: @@ -76,8 +76,7 @@ and run the command:: This command runs a python file stored in the ``src/utilities`` folder. The ``pip install`` process automatically creates this console command in the current python environment to call this python file. The file -directly downloads the missing large BSK data files and put them into the local Basilisk python -package installation. +directly downloads the missing BSK data files and put them into a local pooch cache. .. note:: From 749fea88c17e24dfd2830f9105e63e117c021fbc Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 17:30:48 -0700 Subject: [PATCH 13/15] Use optional for Py<3.10 --- src/utilities/supportDataTools/dataFetcher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utilities/supportDataTools/dataFetcher.py b/src/utilities/supportDataTools/dataFetcher.py index 70303dec3d..4c404f2f9d 100644 --- a/src/utilities/supportDataTools/dataFetcher.py +++ b/src/utilities/supportDataTools/dataFetcher.py @@ -21,6 +21,7 @@ import functools import requests import pooch +from typing import Optional from Basilisk.utilities.supportDataTools.registrySnippet import REGISTRY from Basilisk import __version__ From 7cb9ed4af49bda0a3e7e91fe8508c3b04f130fcb Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Sun, 30 Nov 2025 23:38:34 -0700 Subject: [PATCH 14/15] Add new docs page to explain support data --- docs/source/Support/Developer.rst | 1 + .../Support/Developer/addSupportData.rst | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 docs/source/Support/Developer/addSupportData.rst diff --git a/docs/source/Support/Developer.rst b/docs/source/Support/Developer.rst index b1720eee3b..950736c2f5 100644 --- a/docs/source/Support/Developer.rst +++ b/docs/source/Support/Developer.rst @@ -18,3 +18,4 @@ The following support files help with writing Basilisk modules. Developer/UnderstandingBasilisk Developer/migratingBskModuleToBsk2 Developer/MigratingToPython3 + Developer/addSupportData diff --git a/docs/source/Support/Developer/addSupportData.rst b/docs/source/Support/Developer/addSupportData.rst new file mode 100644 index 0000000000..706e418bff --- /dev/null +++ b/docs/source/Support/Developer/addSupportData.rst @@ -0,0 +1,144 @@ +.. _addSupportData: + +Adding Support Data Files +========================= + +This page explains how to correctly add new files to the ``supportData`` +directory so they integrate with Basilisk's Pooch-based data management system +and are versioned on GitHub. + +Basilisk **does not ship** supportData files inside the wheel. +Instead, all files are registered and fetched on demand. + +When you add or update support data files, three major parts may need updating: + +1. ``supportData/`` directory (the files themselves) +2. ``makeRegistry.py`` (to regenerate the MD5 registry) +3. ``dataFetcher.py`` (to map the file into enums/category paths) + + +---------------------------------------------------------------------- +Folder Layout +---------------------------------------------------------------------- + +Files must live inside: + +:: + + supportDataTools// + +Where ```` must match one of the following: + +- ``AlbedoData`` +- ``AtmosphereData`` +- ``DentonGEO`` +- ``EphemerisData`` +- ``LocalGravData`` +- ``MagneticField`` + +If you add a new sub-category, **you must also add it to** +``dataFetcher.py`` (explained in Step 4). + +---------------------------------------------------------------------- +Step 1 — Add the File to supportData/ +---------------------------------------------------------------------- + +Place your file under the appropriate category, e.g.: + +:: + + supportDataTools/AtmosphereData/NewMarsAtmosphere2025.csv + +Make sure the file is not covered by any ignore patterns +(see ``makeRegistry.py``). + +---------------------------------------------------------------------- +Step 2 — Ensure makeRegistry.py will include the file +---------------------------------------------------------------------- + +The registry generator ignores certain patterns: + +.. code-block:: python + + IGNORE_PATTERNS = ( + "__pycache__", + ".pyc", + "__init__.py", + "*.bsp", + ) + +If your file accidentally matches a pattern, remove or update the entry. + +---------------------------------------------------------------------- +Step 3 — Regenerate the registrySnippet.py file +---------------------------------------------------------------------- + +From the **project root**, run: + +:: + + python src/utilities/supportDataTools/makeRegistry.py > src/utilities/supportDataTools/registrySnippet.py + +This writes a dictionary of: + +- file to MD5 hash +- symbols used by Pooch to verify downloads + +Commit the changed ``registrySnippet.py``. + +---------------------------------------------------------------------- +Step 4 — Update dataFetcher.py (Enums and Base Paths) +---------------------------------------------------------------------- + +In ``dataFetcher.py``, each category has: + +1. An Enum listing files +2. A base path value +3. A mapping in ``CATEGORY_BASE_PATHS`` + +For example, adding ``NewMarsAtmosphere2025.csv`` requires: + +1. Add to the correct Enum: + +.. code-block:: python + + class AtmosphereData(Enum): + NewMarsAtmosphere2025 = "NewMarsAtmosphere2025.csv" + +2. Ensure the base path exists: + +.. code-block:: python + + ATMOSPHERE_DATA_BASE_PATH = "supportData/AtmosphereData/" + +3. Ensure the category name → base path map includes it: + +.. code-block:: python + + CATEGORY_BASE_PATHS = { + "AtmosphereData": ATMOSPHERE_DATA_BASE_PATH, + ... + } + + +---------------------------------------------------------------------- +External Data Sources +---------------------------------------------------------------------- + +Some kernel files are **not in the Git repo** and **should not be hashed**: + +.. code-block:: python + + EXTERNAL_KERNEL_URLS = { + "supportData/EphemerisData/de430.bsp": "https://naif.jpl.nasa.gov/...", + ... + } + +These entries automatically override registry hashes: + +.. code-block:: python + + for key in EXTERNAL_KERNEL_URLS: + REGISTRY[key] = None + +This prevents MD5 failures when NAIF updates files. From 275cdafcf9f03a9f7ea6f4c92e4ea44ee9103815 Mon Sep 17 00:00:00 2001 From: Reece Humphreys Date: Mon, 1 Dec 2025 08:45:21 -0700 Subject: [PATCH 15/15] Update release notes --- docs/source/Support/bskReleaseNotes.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/Support/bskReleaseNotes.rst b/docs/source/Support/bskReleaseNotes.rst index 1f4d52f181..02780e8b81 100644 --- a/docs/source/Support/bskReleaseNotes.rst +++ b/docs/source/Support/bskReleaseNotes.rst @@ -26,6 +26,10 @@ Basilisk Release Notes Version |release| ----------------- +- Migrated supportData handling to a Pooch-based fetch system. +- Removed supportData files from wheels and source distributions to reduce package size. +- Added automatic MD5 registry generation via ``makeRegistry.py`` for versioned supportData. +- Updated documentation to include workflow for adding new supportData files and regenerating the registry. - Removed automated version bumping on merges to develop. Version bumps will be handled manually moving forward. - Added SWIG as a python package dependency in ``requirements_dev.txt`` and updated installation instructions. - Updated CI to no longer automatically publish git tags and publish wheels to PyPI. This process will be handled manually.