2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2020-09-20 09:39:52 -07:00
|
|
|
|
2025-11-07 19:55:43 -05:00
|
|
|
package org.wpilib.simulation;
|
2020-09-20 09:39:52 -07:00
|
|
|
|
2025-11-07 19:55:43 -05:00
|
|
|
import org.wpilib.math.linalg.Matrix;
|
|
|
|
|
import org.wpilib.math.util.Num;
|
|
|
|
|
import org.wpilib.math.util.StateSpaceUtil;
|
|
|
|
|
import org.wpilib.math.numbers.N1;
|
|
|
|
|
import org.wpilib.math.system.LinearSystem;
|
2020-12-29 22:45:16 -08:00
|
|
|
import org.ejml.MatrixDimensionException;
|
|
|
|
|
import org.ejml.simple.SimpleMatrix;
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* This class helps simulate linear systems. To use this class, do the following in the {@link
|
2025-11-07 19:55:43 -05:00
|
|
|
* org.wpilib.opmode.IterativeRobotBase#simulationPeriodic} method.
|
2020-09-20 09:39:52 -07:00
|
|
|
*
|
|
|
|
|
* <p>Call {@link #setInput(double...)} with the inputs to the system (usually voltage).
|
|
|
|
|
*
|
|
|
|
|
* <p>Call {@link #update} to update the simulation.
|
|
|
|
|
*
|
2020-10-16 00:00:45 -04:00
|
|
|
* <p>Set simulated sensor readings with the simulated positions in {@link #getOutput()}
|
2020-09-20 09:39:52 -07:00
|
|
|
*
|
2024-01-01 22:56:23 -08:00
|
|
|
* @param <States> Number of states of the system.
|
|
|
|
|
* @param <Inputs> Number of inputs to the system.
|
|
|
|
|
* @param <Outputs> Number of outputs of the system.
|
2020-09-20 09:39:52 -07:00
|
|
|
*/
|
2020-10-16 00:00:45 -04:00
|
|
|
public class LinearSystemSim<States extends Num, Inputs extends Num, Outputs extends Num> {
|
2024-01-05 07:35:59 -08:00
|
|
|
/** The plant that represents the linear system. */
|
2020-09-20 09:39:52 -07:00
|
|
|
protected final LinearSystem<States, Inputs, Outputs> m_plant;
|
|
|
|
|
|
2024-01-05 07:35:59 -08:00
|
|
|
/** State vector. */
|
2020-09-20 09:39:52 -07:00
|
|
|
protected Matrix<States, N1> m_x;
|
2024-01-05 07:35:59 -08:00
|
|
|
|
|
|
|
|
/** Input vector. */
|
2020-09-20 09:39:52 -07:00
|
|
|
protected Matrix<Inputs, N1> m_u;
|
2020-10-16 00:00:45 -04:00
|
|
|
|
2024-01-05 07:35:59 -08:00
|
|
|
/** Output vector. */
|
|
|
|
|
protected Matrix<Outputs, N1> m_y;
|
|
|
|
|
|
|
|
|
|
/** The standard deviations of measurements, used for adding noise to the measurements. */
|
2020-09-20 09:39:52 -07:00
|
|
|
protected final Matrix<Outputs, N1> m_measurementStdDevs;
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Creates a simulated generic linear system with measurement noise.
|
2020-09-20 09:39:52 -07:00
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param system The system being controlled.
|
2024-05-15 13:40:30 -04:00
|
|
|
* @param measurementStdDevs Standard deviations of measurements. Can be empty if no noise is
|
|
|
|
|
* desired. If present must have same number of items as Outputs
|
2020-09-20 09:39:52 -07:00
|
|
|
*/
|
2020-12-29 22:45:16 -08:00
|
|
|
public LinearSystemSim(
|
2024-05-15 13:40:30 -04:00
|
|
|
LinearSystem<States, Inputs, Outputs> system, double... measurementStdDevs) {
|
|
|
|
|
if (measurementStdDevs.length != 0 && measurementStdDevs.length != system.getC().getNumRows()) {
|
|
|
|
|
throw new MatrixDimensionException(
|
|
|
|
|
"Malformed measurementStdDevs! Got "
|
|
|
|
|
+ measurementStdDevs.length
|
|
|
|
|
+ " elements instead of "
|
|
|
|
|
+ system.getC().getNumRows());
|
|
|
|
|
}
|
2020-09-20 09:39:52 -07:00
|
|
|
this.m_plant = system;
|
2024-05-15 13:40:30 -04:00
|
|
|
if (measurementStdDevs.length == 0) {
|
|
|
|
|
m_measurementStdDevs = new Matrix<>(new SimpleMatrix(system.getC().getNumRows(), 1));
|
|
|
|
|
} else {
|
|
|
|
|
m_measurementStdDevs = new Matrix<>(new SimpleMatrix(measurementStdDevs));
|
|
|
|
|
}
|
2020-09-20 09:39:52 -07:00
|
|
|
m_x = new Matrix<>(new SimpleMatrix(system.getA().getNumRows(), 1));
|
|
|
|
|
m_u = new Matrix<>(new SimpleMatrix(system.getB().getNumCols(), 1));
|
|
|
|
|
m_y = new Matrix<>(new SimpleMatrix(system.getC().getNumRows(), 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates the simulation.
|
|
|
|
|
*
|
2025-02-10 07:23:04 -08:00
|
|
|
* @param dt The time between updates in seconds.
|
2020-09-20 09:39:52 -07:00
|
|
|
*/
|
2025-02-10 07:23:04 -08:00
|
|
|
public void update(double dt) {
|
2025-02-19 01:49:28 -05:00
|
|
|
// Update x. By default, this is the linear system dynamics xₖ₊₁ = Axₖ + Buₖ.
|
2025-02-10 07:23:04 -08:00
|
|
|
m_x = updateX(m_x, m_u, dt);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
2025-02-19 01:49:28 -05:00
|
|
|
// yₖ = Cxₖ + Duₖ
|
2020-09-20 09:39:52 -07:00
|
|
|
m_y = m_plant.calculateY(m_x, m_u);
|
2020-10-16 00:00:45 -04:00
|
|
|
|
|
|
|
|
// Add measurement noise.
|
2020-09-20 09:39:52 -07:00
|
|
|
if (m_measurementStdDevs != null) {
|
|
|
|
|
m_y = m_y.plus(StateSpaceUtil.makeWhiteNoiseVector(m_measurementStdDevs));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Returns the current output of the plant.
|
|
|
|
|
*
|
|
|
|
|
* @return The current output of the plant.
|
|
|
|
|
*/
|
|
|
|
|
public Matrix<Outputs, N1> getOutput() {
|
2020-09-20 09:39:52 -07:00
|
|
|
return m_y;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Returns an element of the current output of the plant.
|
|
|
|
|
*
|
|
|
|
|
* @param row The row to return.
|
|
|
|
|
* @return An element of the current output of the plant.
|
|
|
|
|
*/
|
|
|
|
|
public double getOutput(int row) {
|
2020-09-20 09:39:52 -07:00
|
|
|
return m_y.get(row, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Sets the system inputs (usually voltages).
|
|
|
|
|
*
|
|
|
|
|
* @param u The system inputs.
|
|
|
|
|
*/
|
2020-09-20 09:39:52 -07:00
|
|
|
public void setInput(Matrix<Inputs, N1> u) {
|
2024-05-15 13:40:30 -04:00
|
|
|
this.m_u = u;
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Sets the system inputs.
|
|
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param row The row in the input matrix to set.
|
2020-10-16 00:00:45 -04:00
|
|
|
* @param value The value to set the row to.
|
|
|
|
|
*/
|
2020-09-20 09:39:52 -07:00
|
|
|
public void setInput(int row, double value) {
|
|
|
|
|
m_u.set(row, 0, value);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Sets the system inputs.
|
|
|
|
|
*
|
|
|
|
|
* @param u An array of doubles that represent the inputs of the system.
|
|
|
|
|
*/
|
2020-09-20 09:39:52 -07:00
|
|
|
public void setInput(double... u) {
|
|
|
|
|
if (u.length != m_u.getNumRows()) {
|
2020-12-29 22:45:16 -08:00
|
|
|
throw new MatrixDimensionException(
|
|
|
|
|
"Malformed input! Got " + u.length + " elements instead of " + m_u.getNumRows());
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
m_u = new Matrix<>(new SimpleMatrix(m_u.getNumRows(), 1, true, u));
|
2024-04-04 12:19:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the current input of the plant.
|
|
|
|
|
*
|
|
|
|
|
* @return The current input of the plant.
|
|
|
|
|
*/
|
|
|
|
|
public Matrix<Inputs, N1> getInput() {
|
|
|
|
|
return m_u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an element of the current input of the plant.
|
|
|
|
|
*
|
|
|
|
|
* @param row The row to return.
|
|
|
|
|
* @return An element of the current input of the plant.
|
|
|
|
|
*/
|
|
|
|
|
public double getInput(int row) {
|
|
|
|
|
return m_u.get(row, 0);
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
|
2020-09-27 15:26:50 -07:00
|
|
|
/**
|
|
|
|
|
* Sets the system state.
|
|
|
|
|
*
|
2020-10-16 00:00:45 -04:00
|
|
|
* @param state The new state.
|
2020-09-27 15:26:50 -07:00
|
|
|
*/
|
|
|
|
|
public void setState(Matrix<States, N1> state) {
|
2020-09-20 09:39:52 -07:00
|
|
|
m_x = state;
|
2025-02-19 01:49:28 -05:00
|
|
|
|
|
|
|
|
// Update the output to reflect the new state.
|
|
|
|
|
//
|
|
|
|
|
// yₖ = Cxₖ + Duₖ
|
|
|
|
|
m_y = m_plant.calculateY(m_x, m_u);
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
|
2020-10-16 00:00:45 -04:00
|
|
|
/**
|
|
|
|
|
* Updates the state estimate of the system.
|
|
|
|
|
*
|
|
|
|
|
* @param currentXhat The current state estimate.
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param u The system inputs (usually voltage).
|
2025-02-10 07:23:04 -08:00
|
|
|
* @param dt The time difference between controller updates in seconds.
|
2020-10-16 00:00:45 -04:00
|
|
|
* @return The new state.
|
|
|
|
|
*/
|
2020-12-29 22:45:16 -08:00
|
|
|
protected Matrix<States, N1> updateX(
|
2025-02-10 07:23:04 -08:00
|
|
|
Matrix<States, N1> currentXhat, Matrix<Inputs, N1> u, double dt) {
|
|
|
|
|
return m_plant.calculateX(currentXhat, u, dt);
|
2020-10-16 00:00:45 -04:00
|
|
|
}
|
2020-12-28 13:03:31 -08:00
|
|
|
|
|
|
|
|
/**
|
2024-05-15 13:40:30 -04:00
|
|
|
* Clamp the input vector such that no element exceeds the maximum allowed value. If any does, the
|
2020-12-29 22:45:16 -08:00
|
|
|
* relative magnitudes of the input will be maintained.
|
2020-12-28 13:03:31 -08:00
|
|
|
*
|
2024-05-15 13:40:30 -04:00
|
|
|
* @param maxInput The maximum magnitude of the input vector after clamping.
|
2020-12-28 13:03:31 -08:00
|
|
|
*/
|
2024-05-15 13:40:30 -04:00
|
|
|
protected void clampInput(double maxInput) {
|
|
|
|
|
m_u = StateSpaceUtil.desaturateInputVector(m_u, maxInput);
|
2020-12-28 13:03:31 -08:00
|
|
|
}
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|