Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor GaussMarkov sensor noise handling #881

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/Support/bskKnownIssues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Version |release|
- If configuring and building Basilisk directly with ``conan install`` and ``build`` commands,
the ``-if dist3/conan`` argument is no longer needed. The Basilisk install location is
setup with ``conan 2`` arguments inside ``conanfile.py``.
- Sensor noise models were not being initialized correctly in sensor models such as
:ref:`magnetometer` and :ref:`simpleVoltEstimator` modules. This is now fixed in the current release.
- Propagation matrices were private in the :ref:`simpleVoltEstimator` and :ref:`starTracker` modules.
This is now fixed in the current release by the addition of public methods to set and get the propagation matrices.


Version 2.5.0
Expand Down
7 changes: 6 additions & 1 deletion docs/source/Support/bskReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,14 @@ Version |release|
``%include "simulation/dynamics/_GeneralModuleFiles/dynParamManager.i"`` instead of
``%include "simulation/dynamics/_GeneralModuleFiles/dynParamManager.h"``. See
``src/simulation/dynamics/dragEffector/dragDynamicEffector.i`` for an example.

- Update CI Linux build with ``opNav`` to use Ubuntu 22.04, not latest (i.e. 24.02). The latter does not
support directly Python 3.11, and Basilisk does not support Python 3.13 yet.
- Resolved inconstencies in sensor noise handling for the :ref:`imuSensor`, :ref:`coarseSunsensor`,
:ref:`magnetometer`, :ref:`starTracker`, and :ref:`simpleVoltEstimator` modules.
- Added setter and getter methods for the propagation matrices in the :ref:`simpleVoltEstimator`
and :ref:`starTracker` modules as their ``Amatrix`` attributes were private.
- Name change warning added to module documentation for the ``imuSensor`` ``walkBounds`` attribute to ``errorBounds``
and a note on specifying sensor properties in :ref:`scenarioGaussMarkovRandomWalk`.


Version 2.5.0 (Sept. 30, 2024)
Expand Down
9 changes: 7 additions & 2 deletions examples/scenarioGaussMarkovRandomWalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@

Both IMUs use the same process noise level (P Matrix) to ensure comparable noise magnitudes.

Note that any sensors using the ``GaussMarkov`` noise model should be configured with
user-defined configuration parameters such as ``walkBounds`` and ``AMatrix``. Otherwise,
the default values will be used and they may not be appropriate for the sensor the user
is trying to model.

Illustration of Simulation Results
-----------------------------------

Expand Down Expand Up @@ -152,7 +157,7 @@ def run(show_plots, processNoiseLevel=0.5, walkBounds=3.0):
[0.0, -0.1, 0.0],
[0.0, 0.0, -0.1]
]
imuSensor1.walkBoundsGyro = [walkBounds, walkBounds, walkBounds]
imuSensor1.setWalkBoundsGyro(np.array([walkBounds, walkBounds, walkBounds], dtype=np.float64))
imuSensor1.applySensorErrors = True
imuSensor1.scStateInMsg.subscribeTo(scObject.scStateOutMsg)

Expand Down Expand Up @@ -183,7 +188,7 @@ def run(show_plots, processNoiseLevel=0.5, walkBounds=3.0):

scSim.InitializeSimulation()

# Set IMU2's A Matrix to zero AFTER initialization
# Set IMU2's A Matrix to zero to demonstrate different error propagation behavior.
imuSensor2.AMatrixGyro = [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
Expand Down
30 changes: 30 additions & 0 deletions src/simulation/sensors/coarseSunSensor/coarseSunSensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ CoarseSunSensor::CoarseSunSensor()
this->sunVisibilityFactor.shadowFactor = 1.0;
this->sunDistanceFactor = 1.0;
this->dcm_PB.setIdentity(3,3);
this->propagationMatrix.resize(1);
this->propagationMatrix(0) = 1.0;
return;
}

Expand Down Expand Up @@ -170,6 +172,9 @@ void CoarseSunSensor::Reset(uint64_t CurrentSimNanos)
satBounds(0,1) = this->maxOutput;
this->saturateUtility.setBounds(satBounds);

// Set up noise model with stored propagation matrix
this->noiseModel.setPropMatrix(this->propagationMatrix);
this->faultNoiseModel.setPropMatrix(this->propagationMatrix);
}

void CoarseSunSensor::readInputMessages()
Expand Down Expand Up @@ -435,3 +440,28 @@ void CSSConstellation::appendCSS(CoarseSunSensor* newSensor) {
sensorList.push_back(newSensor);
return;
}

/*!
Setter for `AMatrix` used for error propagation
@param propMatrix Matrix to set
*/
void CoarseSunSensor::setAMatrix(const Eigen::Matrix<double, -1, 1, 0, -1, 1>& propMatrix)
{
if(propMatrix.rows() != 1 || propMatrix.cols() != 1) {
bskLogger.bskLog(BSK_ERROR, "CoarseSunSensor: Propagation matrix must be 1x1");
return;
}
this->propagationMatrix = propMatrix;
// Set the propagation matrix for both noise models
this->noiseModel.setPropMatrix(propMatrix);
this->faultNoiseModel.setPropMatrix(propMatrix);
}

/*!
Getter for `AMatrix` used for error propagation
@return Current matrix
*/
Eigen::Matrix<double, -1, 1, 0, -1, 1> CoarseSunSensor::getAMatrix() const
{
return this->propagationMatrix;
}
14 changes: 9 additions & 5 deletions src/simulation/sensors/coarseSunSensor/coarseSunSensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class CoarseSunSensor: public SysModel {
void scaleSensorValues(); //!< scale the sensor values
void applySaturation(); //!< apply saturation effects to sensed output (floor and ceiling)
void writeOutputMessages(uint64_t Clock); //!< @brief method to write the output message to the system

public:
ReadFunctor<SpicePlanetStateMsgPayload> sunInMsg; //!< [-] input message for sun data
ReadFunctor<SCStatesMsgPayload> stateInMsg; //!< [-] input message for spacecraft state
Expand All @@ -88,7 +88,7 @@ class CoarseSunSensor: public SysModel {
double kellyFactor; //!< [-] Kelly curve fit for output cosine curve
double fov; //!< [-] rad, field of view half angle
Eigen::Vector3d r_B; //!< [m] position vector in body frame
Eigen::Vector3d r_PB_B; //!< [m] misalignment of CSS platform wrt spacecraft body frame
Eigen::Vector3d r_PB_B; //!< [m] misalignment of CSS platform wrt spacecraft body frame
double senBias; //!< [-] Sensor bias value
double senNoiseStd; //!< [-] Sensor noise value
double faultNoiseStd; //!< [-] Sensor noise value if CSSFAULT_RAND is triggered
Expand All @@ -99,6 +99,9 @@ class CoarseSunSensor: public SysModel {
int CSSGroupID=-1; //!< [-] (optional) CSS group id identifier, -1 means it is not set and default is used
BSKLogger bskLogger; //!< -- BSK Logging

void setAMatrix(const Eigen::Matrix<double, -1, 1, 0, -1, 1>& propMatrix);
Eigen::Matrix<double, -1, 1, 0, -1, 1> getAMatrix() const;

private:
SpicePlanetStateMsgPayload sunData; //!< [-] Unused for now, but including it for future
SCStatesMsgPayload stateCurrent; //!< [-] Current SSBI-relative state
Expand All @@ -107,11 +110,12 @@ class CoarseSunSensor: public SysModel {
GaussMarkov noiseModel; //! [-] Gauss Markov noise generation model
GaussMarkov faultNoiseModel; //! [-] Gauss Markov noise generation model exclusively for CSS fault
Saturate saturateUtility; //! [-] Saturation utility
Eigen::Matrix<double, -1, 1, 0, -1, 1> propagationMatrix; // Store the propagation matrix
};

//!@brief Constellation of coarse sun sensors for aggregating output information
/*! This class is a thin container on top of the above coarse-sun sensor class.
It is used to aggregate the output messages of the coarse sun-sensors into a
/*! This class is a thin container on top of the above coarse-sun sensor class.
It is used to aggregate the output messages of the coarse sun-sensors into a
a single output for use by downstream models.*/
class CSSConstellation: public SysModel {
public:
Expand All @@ -120,7 +124,7 @@ class CSSConstellation: public SysModel {
void Reset(uint64_t CurrentClock); //!< Method for reseting the module
void UpdateState(uint64_t CurrentSimNanos); //!< @brief [-] Main update method for CSS constellation
void appendCSS(CoarseSunSensor *newSensor); //!< @brief [-] Method for adding sensor to list

public:
Message<CSSArraySensorMsgPayload> constellationOutMsg; //!< [-] CSS constellation output message
std::vector<CoarseSunSensor *> sensorList; //!< [-] List of coarse sun sensors in constellation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,9 @@ def unitSimIMU(show_plots, testCase, stopTime, procRate, gyroLSBIn
for j in [0, 1, 2]:
omegaOutNoise[i, j] = omegaOut[i, j + 1] - omegaOut[i-1, j + 1]

# Use a more lenient accuracy threshold for noise comparisons
noiseAccuracy = 0.5 # Allows for up to 50% deviation

# Compare noise standard deviations with expected values
for i, (actual, expected, name) in enumerate([
(np.std(DRoutNoise[:,0]), senRotNoiseStd*dt, "DRnoise1"),
Expand All @@ -472,7 +475,7 @@ def unitSimIMU(show_plots, testCase, stopTime, procRate, gyroLSBIn
print(f"\nChecking {name}:")
print(f" Actual value: {actual}")
print(f" Expected value: {expected}")
if not unitTestSupport.isDoubleEqualRelative(actual, expected, accuracy):
if not unitTestSupport.isDoubleEqualRelative(actual, expected, noiseAccuracy):
msg = f"FAILED {name}. Expected {expected}, got {actual}. \\\\& &"
testMessages.append(msg)
testFailCount += 1
Expand Down
86 changes: 78 additions & 8 deletions src/simulation/sensors/imuSensor/imuSensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ ImuSensor::ImuSensor()
this->AMatrixGyro.fill(0.0);
this->PMatrixAccel.fill(0.0);
this->AMatrixAccel.fill(0.0);
this->walkBoundsGyro.fill(0.0);
this->walkBoundsAccel.fill(0.0);
this->navErrorsGyro.fill(0.0);
this->navErrorsAccel.fill(0.0);
this->walkBoundsGyro.fill(0.0); // December 2025: rename to errorBoundsGyro
this->walkBoundsAccel.fill(0.0); // December 2025: rename to errorBoundsAccel
this->previous_omega_BN_B.fill(0.0);
this->current_omega_BN_B.fill(0.0);
this->current_nonConservativeAccelpntB_B.fill(0.0);
Expand Down Expand Up @@ -103,8 +101,6 @@ void ImuSensor::Reset(uint64_t CurrentSimNanos)
bskLogger.bskLog(BSK_ERROR, "imuSensor.scStateInMsg was not linked.");
}

this->AMatrixAccel.setIdentity(this->numStates,this->numStates);

//! - Alert the user if the noise matrix was not the right size. That'd be bad.
if(this->PMatrixAccel.cols() != this->numStates || this->PMatrixAccel.rows() != this->numStates)
{
Expand All @@ -115,8 +111,6 @@ void ImuSensor::Reset(uint64_t CurrentSimNanos)
this->errorModelAccel.setRNGSeed(this->RNGSeed);
this->errorModelAccel.setUpperBounds(this->walkBoundsAccel);

this->AMatrixGyro.setIdentity(this->numStates, this->numStates);

//! - Alert the user if the noise matrix was not the right size. That'd be bad.
if(this->PMatrixGyro.rows() != this->numStates || this->PMatrixGyro.cols() != this->numStates)
{
Expand Down Expand Up @@ -416,3 +410,79 @@ void ImuSensor::UpdateState(uint64_t CurrentSimNanos)

return;
}

/*!
Setter for `AMatrixAccel`
@param propMatrix Matrix to set
*/
void ImuSensor::setAMatrixAccel(const Eigen::MatrixXd& propMatrix)
{
this->AMatrixAccel = propMatrix;
this->errorModelAccel.setPropMatrix(propMatrix);
}

/*!
Setter for `AMatrixGyro`
@param propMatrix Matrix to set
*/
void ImuSensor::setAMatrixGyro(const Eigen::MatrixXd& propMatrix)
{
this->AMatrixGyro = propMatrix;
this->errorModelGyro.setPropMatrix(propMatrix);
}

/*!
Getter for `AMatrixAccel`
@return Current matrix
*/
Eigen::MatrixXd ImuSensor::getAMatrixAccel() const
{
return this->AMatrixAccel;
}

/*!
Getter for `AMatrixGyro`
@return Current matrix
*/
Eigen::MatrixXd ImuSensor::getAMatrixGyro() const
{
return this->AMatrixGyro;
}

/*!
Setter for `walkBoundsAccel`
@param bounds Bounds vector to set
*/
void ImuSensor::setWalkBoundsAccel(const Eigen::Vector3d& bounds)
{
this->walkBoundsAccel = bounds;
this->errorModelAccel.setUpperBounds(bounds);
}

/*!
Setter for `walkBoundsGyro`
@param bounds Bounds vector to set
*/
void ImuSensor::setWalkBoundsGyro(const Eigen::Vector3d& bounds)
{
this->walkBoundsGyro = bounds;
this->errorModelGyro.setUpperBounds(bounds);
}

/*!
Getter for `walkBoundsAccel`
@return Current bounds
*/
Eigen::Vector3d ImuSensor::getWalkBoundsAccel() const
{
return this->walkBoundsAccel;
}

/*!
Getter for `walkBoundsGyro`
@return Current bounds
*/
Eigen::Vector3d ImuSensor::getWalkBoundsGyro() const
{
return this->walkBoundsGyro;
}
22 changes: 15 additions & 7 deletions src/simulation/sensors/imuSensor/imuSensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ImuSensor: public SysModel {
public:
ImuSensor();
~ImuSensor();

void Reset(uint64_t CurrentSimNanos);
void UpdateState(uint64_t CurrentSimNanos);
void readInputMessages();
Expand All @@ -59,6 +59,14 @@ class ImuSensor: public SysModel {
void setRoundDirection(roundDirection_t aRound, roundDirection_t oRound);
void set_oSatBounds(Eigen::MatrixXd oSatBounds);
void set_aSatBounds(Eigen::MatrixXd aSatBounds);
void setAMatrixAccel(const Eigen::MatrixXd& propMatrix);
void setAMatrixGyro(const Eigen::MatrixXd& propMatrix);
Eigen::MatrixXd getAMatrixAccel() const;
Eigen::MatrixXd getAMatrixGyro() const;
void setWalkBoundsAccel(const Eigen::Vector3d& bounds);
void setWalkBoundsGyro(const Eigen::Vector3d& bounds);
Eigen::Vector3d getWalkBoundsAccel() const;
Eigen::Vector3d getWalkBoundsGyro() const;

public:
ReadFunctor<SCStatesMsgPayload> scStateInMsg; /*!< input essage name for spacecraft state */
Expand All @@ -73,19 +81,19 @@ class ImuSensor: public SysModel {
bool NominalReady; //!< -- Flag indicating that system is in run
Eigen::Matrix3d PMatrixAccel; //!< [-] Cholesky-decomposition or matrix square root of the covariance matrix to apply errors with
Eigen::Matrix3d AMatrixAccel; //!< [-] AMatrix that we use for error propagation
Eigen::Vector3d walkBoundsAccel;//!< [-] "3-sigma" errors to permit for states
Eigen::Vector3d walkBoundsAccel; //!< [-] Total error bounds for accelerometer states. @warning Will be renamed to errorBoundsAccel in December 2025
Eigen::Vector3d navErrorsAccel; //!< [-] Current navigation errors applied to truth
Eigen::Matrix3d PMatrixGyro; //!< [-] Cholesky-decomposition or matrix square root of the covariance matrix to apply errors with
Eigen::Matrix3d AMatrixGyro; //!< [-] AMatrix that we use for error propagation
Eigen::Vector3d walkBoundsGyro; //!< [-] "3-sigma" errors to permit for states
Eigen::Vector3d walkBoundsGyro; //!< [-] Total error bounds for gyro states. @warning Will be renamed to errorBoundsGyro in December 2025
Eigen::Vector3d navErrorsGyro; //!< [-] Current navigation errors applied to truth

IMUSensorMsgPayload trueValues; //!< [-] total measurement without perturbations
IMUSensorMsgPayload sensedValues; //!< [-] total measurement including perturbations

Eigen::Vector3d accelScale; //!< (-) scale factor for acceleration axes
Eigen::Vector3d gyroScale; //!< (-) scale factors for acceleration axes

Discretize aDisc; //!< (-) instance of discretization utility for linear acceleration
Discretize oDisc; //!< (-) instance of idscretization utility for angular rate
Saturate aSat; //!< (-) instance of saturate utility for linear acceleration
Expand All @@ -100,7 +108,7 @@ class ImuSensor: public SysModel {
SCStatesMsgPayload StateCurrent; //!< -- Current SSBI-relative state
GaussMarkov errorModelAccel; //!< [-] Gauss-markov error states
GaussMarkov errorModelGyro; //!< [-] Gauss-markov error states

Eigen::MRPd previous_sigma_BN; //!< -- sigma_BN from the previous spacecraft message
Eigen::MRPd current_sigma_BN; //!< -- sigma_BN from the most recent spacecraft message
Eigen::Vector3d previous_omega_BN_B; //!< -- omega_BN_B from the previous spacecraft message
Expand All @@ -109,7 +117,7 @@ class ImuSensor: public SysModel {
Eigen::Vector3d current_omegaDot_BN_B; //!< -- omegaDot_BN_B from the curret spacecraft message
Eigen::Vector3d previous_TotalAccumDV_BN_B; //!< -- TotalAccumDV_BN_B from the previous spacecraft message
Eigen::Vector3d current_TotalAccumDV_BN_B; //!< -- TotalAccumDV_BN_B from the current spacecraft message

Eigen::Vector3d accel_SN_P_out; //!< -- rDotDot_SN_P for either next method or output messages
Eigen::Vector3d DV_SN_P_out; //!< -- time step deltaV for either next method or output messages
Eigen::Vector3d omega_PN_P_out; //!< -- omega_PN_P for either next method or output messages
Expand Down
29 changes: 27 additions & 2 deletions src/simulation/sensors/magnetometer/magnetometer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,23 @@ Magnetometer::Magnetometer()
this->senBias.fill(0.0); // Tesla
this->senNoiseStd.fill(-1.0); // Tesla
this->walkBounds.fill(0.0);
this->noiseModel = GaussMarkov(this->numStates);

// Initialize noise model
this->noiseModel = GaussMarkov(this->numStates, this->RNGSeed);

// Initialize noise matrices with defaults
Eigen::MatrixXd nMatrix;
nMatrix.resize(3,3);
nMatrix.setZero();
this->noiseModel.setNoiseMatrix(nMatrix);

Eigen::MatrixXd pMatrix;
pMatrix.setIdentity(3,3);
this->noiseModel.setPropMatrix(pMatrix);

this->noiseModel.setUpperBounds(this->walkBounds);

// Initialize other parameters
this->scaleFactor = 1.0;
this->maxOutput = 1e200; // Tesla
this->minOutput = -1e200; // Tesla
Expand Down Expand Up @@ -67,10 +83,19 @@ void Magnetometer::Reset(uint64_t CurrentSimNanos)
bskLogger.bskLog(BSK_ERROR, "Spacecraft state message name (stateInMsg) is empty.");
}

// Update noise model parameters
this->noiseModel.setUpperBounds(this->walkBounds);
auto nMatrix = (this->senNoiseStd).asDiagonal();

Eigen::MatrixXd nMatrix;
nMatrix.resize(3,3);
nMatrix.setZero();
nMatrix(0,0) = this->senNoiseStd(0);
nMatrix(1,1) = this->senNoiseStd(1);
nMatrix(2,2) = this->senNoiseStd(2);
this->noiseModel.setNoiseMatrix(nMatrix);
this->noiseModel.setRNGSeed(this->RNGSeed);

// Set saturation bounds
Eigen::MatrixXd satBounds;
satBounds.resize(this->numStates, 2);
satBounds(0, 0) = this->minOutput;
Expand Down
Loading
Loading