From e5ee7a57400212bf5918a54788f8a43052868b8c Mon Sep 17 00:00:00 2001 From: Mark Stephenson Date: Fri, 24 Oct 2025 15:03:28 -0600 Subject: [PATCH 1/4] Fuel tank leak rate parameter --- src/simulation/dynamics/FuelTank/fuelTank.cpp | 1 + src/simulation/dynamics/FuelTank/fuelTank.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/simulation/dynamics/FuelTank/fuelTank.cpp b/src/simulation/dynamics/FuelTank/fuelTank.cpp index bab02b0e73..32f7932f21 100644 --- a/src/simulation/dynamics/FuelTank/fuelTank.cpp +++ b/src/simulation/dynamics/FuelTank/fuelTank.cpp @@ -106,6 +106,7 @@ void FuelTank::updateEffectorMassProps(double integTime) { // Mass depletion (call thrusters attached to this tank to get their mDot, and contributions) this->fuelConsumption = 0.0; + this->fuelConsumption += this->fuelLeakRate; for (auto &dynEffector: this->thrDynEffectors) { dynEffector->computeStateContribution(integTime); this->fuelConsumption += dynEffector->stateDerivContribution(0); diff --git a/src/simulation/dynamics/FuelTank/fuelTank.h b/src/simulation/dynamics/FuelTank/fuelTank.h index 22cf883ca5..5224ad0819 100755 --- a/src/simulation/dynamics/FuelTank/fuelTank.h +++ b/src/simulation/dynamics/FuelTank/fuelTank.h @@ -270,6 +270,7 @@ class FuelTank : bool updateOnly = true; //!< -- Sets whether to use update only mass depletion Message fuelTankOutMsg{}; //!< -- fuel tank output message name FuelTankMsgPayload fuelTankMassPropMsg{}; //!< instance of messaging system message struct + double fuelLeakRate{}; //!< [kg/s] rate of fuel leaking from tank; does not produce force private: StateData *omegaState{}; //!< -- state data for omega_BN of the hub From 76cbbe827bd4fd172afc96a7ea7dff75c516af1f Mon Sep 17 00:00:00 2001 From: Mark Stephenson Date: Sat, 25 Oct 2025 09:32:24 -0600 Subject: [PATCH 2/4] Add fuel leak unit test --- .../FuelTank/_UnitTest/test_mass_depletion.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/simulation/dynamics/FuelTank/_UnitTest/test_mass_depletion.py b/src/simulation/dynamics/FuelTank/_UnitTest/test_mass_depletion.py index e56f9b3cd3..37cd33929f 100644 --- a/src/simulation/dynamics/FuelTank/_UnitTest/test_mass_depletion.py +++ b/src/simulation/dynamics/FuelTank/_UnitTest/test_mass_depletion.py @@ -194,5 +194,52 @@ def test_massDepletionTest(show_plots, thrusterConstructor): err_msg="Thruster mass depletion not ramped up") np.testing.assert_allclose(fuelMassDot[-1],0, rtol=1e-12, err_msg="Thruster mass depletion not ramped down") +def test_leakyTank(): + """Module Unit Test""" + scObject = spacecraft.Spacecraft() + scObject.ModelTag = "spacecraftBody" + unitTaskName = "unitTask" + unitProcessName = "TestProcess" + + # Create a sim module as an empty container + unitTestSim = SimulationBaseClass.SimBaseClass() + + # Create test thread + testProcessRate = macros.sec2nano(0.1) + testProc = unitTestSim.CreateNewProcess(unitProcessName) + testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate)) + + # Make Fuel Tank + unitTestSim.fuelTankStateEffector = fuelTank.FuelTank() + tankModel = fuelTank.FuelTankModelConstantVolume() + unitTestSim.fuelTankStateEffector.setTankModel(tankModel) + tankModel.propMassInit = 40.0 + + # Add tank + scObject.addStateEffector(unitTestSim.fuelTankStateEffector) + + # Make the tank leaky + leakRate = 1e-5 # kg/s + unitTestSim.fuelTankStateEffector.fuelLeakRate = leakRate # kg/s + + # Add test module to runtime call list + unitTestSim.AddModelToTask(unitTaskName, unitTestSim.fuelTankStateEffector) + unitTestSim.AddModelToTask(unitTaskName, scObject) + + fuelLog = unitTestSim.fuelTankStateEffector.fuelTankOutMsg.recorder() + unitTestSim.AddModelToTask(unitTaskName, fuelLog) + unitTestSim.InitializeSimulation() + + stopTime = 1000.0 + unitTestSim.ConfigureStopTime(macros.sec2nano(stopTime)) + unitTestSim.ExecuteSimulation() + + fuelMass = fuelLog.fuelMass + fuelMassDot = fuelLog.fuelMassDot + + assert np.allclose(fuelMassDot, -leakRate, rtol=1e-6) + assert np.isclose(fuelMass[-1], 40.0 - stopTime * leakRate, rtol=1e-6) + + if __name__ == "__main__": test_massDepletionTest(True, thrusterDynamicEffector.ThrusterDynamicEffector) From 5201b4638c78c7372aa422f4d7ee65dd40968efa Mon Sep 17 00:00:00 2001 From: Mark Stephenson Date: Sat, 25 Oct 2025 09:33:26 -0600 Subject: [PATCH 3/4] Update release notes --- docs/source/Support/bskReleaseNotes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/Support/bskReleaseNotes.rst b/docs/source/Support/bskReleaseNotes.rst index 1533ecfa28..33002bd070 100644 --- a/docs/source/Support/bskReleaseNotes.rst +++ b/docs/source/Support/bskReleaseNotes.rst @@ -68,6 +68,8 @@ Version |release| - ``ConfigureStopTime`` now supports specifying the stop condition as ``<=`` (default, prior behavior) or ``>=`` (new). The new option is useful when the user wants to ensure that the simulation runs for at least the specified time, instead of at most the specified time. +- Add the ``fuelLeakRate`` parameter to the :ref:`FuelTank` module to simulate fuel leaks that cause a loss of fuel mass + without imparting momentum. Version 2.8.0 (August 30, 2025) From 64a7ba4446e51897b38a44440f236cd00ef3a0e0 Mon Sep 17 00:00:00 2001 From: Mark Stephenson Date: Tue, 18 Nov 2025 17:12:34 -0700 Subject: [PATCH 4/4] Prevent reloading of the same SPICE kernel --- .../spiceInterface/spiceInterface.cpp | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/simulation/environment/spiceInterface/spiceInterface.cpp b/src/simulation/environment/spiceInterface/spiceInterface.cpp index bdcff1241c..d8cca5bb62 100755 --- a/src/simulation/environment/spiceInterface/spiceInterface.cpp +++ b/src/simulation/environment/spiceInterface/spiceInterface.cpp @@ -426,6 +426,8 @@ void SpiceInterface::pullSpiceData(std::vector *spic } } +const int MAXLEN = 256; + /*! This method loads a requested SPICE kernel into the system memory. It is its own method because we have to load several SPICE kernels in for our application. Note that they are stored in the SPICE library and are not @@ -439,6 +441,33 @@ int SpiceInterface::loadSpiceKernel(char *kernelName, const char *dataPath) char *fileName = new char[this->charBufferSize]; SpiceChar *name = new SpiceChar[this->charBufferSize]; + // Check if the kernel is already loaded + SpiceChar fileCompare [MAXLEN]; + SpiceChar filtyp [MAXLEN]; + SpiceChar srcfil [MAXLEN]; + SpiceInt handle; + SpiceBoolean found = SPICEFALSE; + SpiceInt total_kernels; + ktotal_c( "ALL", &total_kernels ); + + for (SpiceInt i = 0; i < total_kernels; i++ ) + { + // Get the i-th loaded kernel information + kdata_c(i, "ALL", MAXLEN, MAXLEN, MAXLEN, fileCompare, filtyp, srcfil, &handle, &found); + + if (found){ + // Check if kernelName is at the end of the file string + size_t fileLen = strlen(fileCompare); + size_t kernelLen = strlen(kernelName); + if (fileLen >= kernelLen && + strcmp(fileCompare + fileLen - kernelLen, kernelName) == 0) + { + // The kernel_to_check has already been successfully loaded + return 0; + } + } + } + //! - The required calls come from the SPICE documentation. //! - The most critical call is furnsh_c strcpy(name, "REPORT");