diff --git a/docs/source/Support/bskReleaseNotes.rst b/docs/source/Support/bskReleaseNotes.rst index cea93017a2..5f7f1a2002 100644 --- a/docs/source/Support/bskReleaseNotes.rst +++ b/docs/source/Support/bskReleaseNotes.rst @@ -79,6 +79,7 @@ Version |release| the added setters and getters must be used. - Fixed a bug in which the ``MtbEffector.py`` module was not being imported correctly in Python due to lack of ``swig_eigen.i`` include file in ``MtbEffector.i``. +- Added the capability to simulate a fault in the :ref:`simpleBattery` module that reduces the actual storage capacity without directly altering the stated capacity. - Cleaned up what python packages are required to build BSK (``requirements_dev.txt``), to run BSK (``requirements.txt``) and to build BSK documentation (``requirements_doc.txt``). - The BSK install instructions are updated to ask users to install by first ``pip`` installing build diff --git a/src/simulation/power/simpleBattery/_UnitTest/test_simpleBatteryFault.py b/src/simulation/power/simpleBattery/_UnitTest/test_simpleBatteryFault.py new file mode 100644 index 0000000000..090df4e877 --- /dev/null +++ b/src/simulation/power/simpleBattery/_UnitTest/test_simpleBatteryFault.py @@ -0,0 +1,119 @@ +# +# ISC License +# +# Copyright (c) 2016, 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. +# + +import inspect +import os +import pytest +import numpy as np + +filename = inspect.getframeinfo(inspect.currentframe()).filename +path = os.path.dirname(os.path.abspath(filename)) +bskName = 'Basilisk' +splitPath = path.split(bskName) + +from Basilisk.utilities import SimulationBaseClass +from Basilisk.architecture import messaging +from Basilisk.simulation import simpleBattery +from Basilisk.utilities import macros + +params_set_data = [(5., 5., 5., 10., 0.3), + (1., 1., 5., 10., 0.3), + (5, 5, 5, 10, 1e-3), + (5., -5., 5., 10., 0.3), + (5., 5., 5., 10., -0.3), + (5., 5., 5., 10., 1.3)] + +@pytest.mark.parametrize( + "storedChargeInit, netPower_1, netPower_2, batteryCapacity, faultCapacityRatio", + params_set_data) + +def test_storage_limits(storedChargeInit, netPower_1, netPower_2, batteryCapacity, faultCapacityRatio): + """ + **Validation Test Description** + 1. Check if the battery capacity matches its nominal value when a fault occurs. + 2. Verify that the charged capacity does not exceed the faulted capacity or drop below zero. + + :param show_plots: Not used; no plots to be shown. + + :return: + """ + + unitTaskName = "unitTask" # arbitrary name (don't change) + unitProcessName = "TestProcess" # arbitrary name (don't change) + + # Create a sim module as an empty container + unitTestSim = SimulationBaseClass.SimBaseClass() + + # Create test thread + testProcessRate = macros.sec2nano(0.1) # update process rate update time + testProc = unitTestSim.CreateNewProcess(unitProcessName) + testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate)) + + test_battery = simpleBattery.SimpleBattery() + test_battery.storedCharge_Init = storedChargeInit + test_battery.storageCapacity = batteryCapacity + test_battery.fault = True + test_battery.faultCapacityRatio = faultCapacityRatio + + powerMsg1 = messaging.PowerNodeUsageMsgPayload() + powerMsg1.netPower = netPower_1 + pw1Msg = messaging.PowerNodeUsageMsg().write(powerMsg1) + powerMsg2 = messaging.PowerNodeUsageMsgPayload() + powerMsg2.netPower = netPower_2 + pw2Msg = messaging.PowerNodeUsageMsg().write(powerMsg2) + + # Test the addNodeToStorage method: + test_battery.addPowerNodeToModel(pw1Msg) + test_battery.addPowerNodeToModel(pw2Msg) + + unitTestSim.AddModelToTask(unitTaskName, test_battery) + + dataLog = test_battery.batPowerOutMsg.recorder() + unitTestSim.AddModelToTask(unitTaskName, dataLog) + + unitTestSim.InitializeSimulation() + unitTestSim.ConfigureStopTime(macros.sec2nano(5.0)) + + unitTestSim.ExecuteSimulation() + + storedChargeLog = dataLog.storageLevel + capacityLog = dataLog.storageCapacity + + # Check 1 - is capacity logged correctly? + for ind in range(0,len(capacityLog)): + capacity = capacityLog[ind] + np.testing.assert_allclose(capacity, batteryCapacity, atol=1e-4, + err_msg=("FAILED: SimpleBattery with fault did not correctly log the capacity.")) + + # Check 2 - is stored power correct? (Capacity reduced by 30% due to fault) + for ind in range(0,len(storedChargeLog)): + storedCharge = storedChargeLog[ind] + assert storedCharge <= capacityLog[ind] * test_battery.faultCapacityRatio, ( + "FAILED: SimpleBattery's stored charge exceeded its faulted capacity.") + + assert storedCharge >= 0., ( + "FAILED: SimpleBattery's stored charge was negative.") + + +if __name__ == "__main__": + storedChargeInit = 1. + netPower_1 = 1. + netPower_2 = 5. + batteryCapacity = 10. + faultCapacityRatio = 0.3 + test_storage_limits(storedChargeInit, netPower_1, netPower_2, batteryCapacity, faultCapacityRatio) diff --git a/src/simulation/power/simpleBattery/simpleBattery.cpp b/src/simulation/power/simpleBattery/simpleBattery.cpp index c0d7406347..04aaf888c8 100644 --- a/src/simulation/power/simpleBattery/simpleBattery.cpp +++ b/src/simulation/power/simpleBattery/simpleBattery.cpp @@ -59,6 +59,16 @@ void SimpleBattery::evaluateBatteryModel(PowerStorageStatusMsgPayload *msg) { this->storedCharge = 0; } + if(this->fault == true) { + if(this->faultCapacityRatio < 0) + { + this->faultCapacityRatio = 1.0; + } + if(this->storedCharge > this->storageCapacity * this->faultCapacityRatio) { + this->storedCharge = this->storageCapacity * this->faultCapacityRatio; + } + } + msg->storageCapacity = this->storageCapacity; msg->storageLevel = this->storedCharge; msg->currentNetPower = this->currentPowerSum; diff --git a/src/simulation/power/simpleBattery/simpleBattery.h b/src/simulation/power/simpleBattery/simpleBattery.h index 34a199245b..3721db8d90 100644 --- a/src/simulation/power/simpleBattery/simpleBattery.h +++ b/src/simulation/power/simpleBattery/simpleBattery.h @@ -39,6 +39,8 @@ class SimpleBattery: public PowerStorageBase { public: double storageCapacity; //!< [W-s] Battery capacity in Watt-seconds (Joules). + bool fault = false; //!< Fault flag + double faultCapacityRatio = 1.0; //!< Fault capacity ratio BSKLogger bskLogger; //!< -- BSK Logging }; diff --git a/src/simulation/power/simpleBattery/simpleBattery.rst b/src/simulation/power/simpleBattery/simpleBattery.rst index 0d96025cb7..706f7914f6 100644 --- a/src/simulation/power/simpleBattery/simpleBattery.rst +++ b/src/simulation/power/simpleBattery/simpleBattery.rst @@ -36,3 +36,8 @@ The next step is to attach one or more :ref:`PowerNodeUsageMsgPayload` instances For more information on how to set up and use this module, see the simple power system example :ref:`scenarioPowerDemo`. + +To simulate a battery capacity fault that reduces the actual storage capacity (without directly altering the stated capacity), users must set up the fault flag:: + + battery.fault = True + battery.faultCapacityRatio = 0.5 # 50% of the original capacity \ No newline at end of file