// 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.
package org.wpilib.math.system;
import org.wpilib.math.linalg.MatBuilder;
import org.wpilib.math.numbers.N1;
import org.wpilib.math.numbers.N2;
import org.wpilib.math.util.Nat;
/** Linear system factories. */
public final class Models {
private Models() {
// Utility class
}
/**
* Creates a flywheel state-space model from physical constants.
*
*
The states are [angular velocity], the inputs are [voltage], and the outputs are [angular
* velocity].
*
* @param motor The motor (or gearbox) attached to the flywheel.
* @param J The moment of inertia J of the flywheel.
* @param gearing Gear ratio from motor to flywheel (greater than 1 is a reduction).
* @return Flywheel state-space model.
* @throws IllegalArgumentException if J <= 0 or gearing <= 0.
*/
public static LinearSystem flywheelFromPhysicalConstants(
DCMotor motor, double J, double gearing) {
if (J <= 0.0) {
throw new IllegalArgumentException("J must be greater than zero.");
}
if (gearing <= 0.0) {
throw new IllegalArgumentException("gearing must be greater than zero.");
}
var A =
MatBuilder.fill(
Nat.N1(), Nat.N1(), -Math.pow(gearing, 2) * motor.Kt / (motor.Kv * motor.R * J));
var B = MatBuilder.fill(Nat.N1(), Nat.N1(), gearing * motor.Kt / (motor.R * J));
var C = MatBuilder.fill(Nat.N1(), Nat.N1(), 1.0);
var D = MatBuilder.fill(Nat.N1(), Nat.N1(), 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates a flywheel state-space model from SysId constants kᵥ (V/(rad/s)) and kₐ (V/(rad/s²))
* from the feedforward model u = kᵥv + kₐa.
*
* The states are [velocity], the inputs are [voltage], and the outputs are [velocity].
*
* @param kV The velocity gain, in V/(rad/s).
* @param kA The acceleration gain, in V/(rad/s²).
* @return Flywheel state-space model.
* @throws IllegalArgumentException if kV < 0 or kA <= 0.
* @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem flywheelFromSysId(double kV, double kA) {
if (kV < 0.0) {
throw new IllegalArgumentException("Kv must be greater than or equal to zero.");
}
if (kA <= 0.0) {
throw new IllegalArgumentException("Ka must be greater than zero.");
}
var A = MatBuilder.fill(Nat.N1(), Nat.N1(), -kV / kA);
var B = MatBuilder.fill(Nat.N1(), Nat.N1(), 1.0 / kA);
var C = MatBuilder.fill(Nat.N1(), Nat.N1(), 1.0);
var D = MatBuilder.fill(Nat.N1(), Nat.N1(), 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates an elevator state-space model from physical constants.
*
* The states are [position, velocity], the inputs are [voltage], and the outputs are
* [position, velocity].
*
* @param motor The motor (or gearbox) attached to the carriage.
* @param mass The mass of the elevator carriage, in kilograms.
* @param radius The radius of the elevator's driving drum, in meters.
* @param gearing Gear ratio from motor to carriage (greater than 1 is a reduction).
* @return Elevator state-space model.
* @throws IllegalArgumentException if mass <= 0, radius <= 0, or gearing <= 0.
*/
public static LinearSystem elevatorFromPhysicalConstants(
DCMotor motor, double mass, double radius, double gearing) {
if (mass <= 0.0) {
throw new IllegalArgumentException("mass must be greater than zero.");
}
if (radius <= 0.0) {
throw new IllegalArgumentException("radius must be greater than zero.");
}
if (gearing <= 0.0) {
throw new IllegalArgumentException("gearing must be greater than zero.");
}
var A =
MatBuilder.fill(
Nat.N2(),
Nat.N2(),
0.0,
1.0,
0.0,
-Math.pow(gearing, 2) * motor.Kt / (motor.R * Math.pow(radius, 2) * mass * motor.Kv));
var B =
MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, gearing * motor.Kt / (motor.R * radius * mass));
var C = MatBuilder.fill(Nat.N2(), Nat.N2(), 1.0, 0.0, 0.0, 1.0);
var D = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates an elevator state-space model from SysId constants kᵥ (V/(m/s)) and kₐ (V/(m/s²)) from
* the feedforward model u = kᵥv + kₐa.
*
* The states are [position, velocity], the inputs are [voltage], and the outputs are
* [position, velocity].
*
* @param kV The velocity gain, in V/(m/s).
* @param kA The acceleration gain, in V/(m/s²).
* @return Elevator state-space model.
* @throws IllegalArgumentException if kV < 0 or kA <= 0.
* @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem elevatorFromSysId(double kV, double kA) {
if (kV < 0.0) {
throw new IllegalArgumentException("Kv must be greater than or equal to zero.");
}
if (kA <= 0.0) {
throw new IllegalArgumentException("Ka must be greater than zero.");
}
var A = MatBuilder.fill(Nat.N2(), Nat.N2(), 0.0, 1.0, 0.0, -kV / kA);
var B = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, 1.0 / kA);
var C = MatBuilder.fill(Nat.N2(), Nat.N2(), 1.0, 0.0, 0.0, 1.0);
var D = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Create a single-jointed arm state-space model from physical constants.
*
* The states are [angle, angular velocity], the inputs are [voltage], and the outputs are
* [angle, angular velocity].
*
* @param motor The motor (or gearbox) attached to the arm.
* @param J The moment of inertia J of the arm.
* @param gearing Gear ratio from motor to arm (greater than 1 is a reduction).
* @return Single-jointed arm state-space model.
* @throws IllegalArgumentException if J <= 0 or gearing <= 0.
*/
public static LinearSystem singleJointedArmFromPhysicalConstants(
DCMotor motor, double J, double gearing) {
if (J <= 0.0) {
throw new IllegalArgumentException("J must be greater than zero.");
}
if (gearing <= 0.0) {
throw new IllegalArgumentException("gearing must be greater than zero.");
}
var A =
MatBuilder.fill(
Nat.N2(),
Nat.N2(),
0.0,
1.0,
0.0,
-Math.pow(gearing, 2) * motor.Kt / (motor.Kv * motor.R * J));
var B = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, gearing * motor.Kt / (motor.R * J));
var C = MatBuilder.fill(Nat.N2(), Nat.N2(), 1.0, 0.0, 0.0, 1.0);
var D = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates a single-jointed arm state-space model from SysId constants kᵥ (V/(rad/s)) and kₐ
* (V/(rad/s²)) from the feedforward model u = kᵥv + kₐa.
*
* The states are [position, velocity], the inputs are [voltage], and the outputs are
* [position, velocity].
*
* @param kV The velocity gain, in volts/(unit/sec).
* @param kA The acceleration gain, in volts/(unit/sec²).
* @return Single-jointed arm state-space model.
* @throws IllegalArgumentException if kV < 0 or kA <= 0.
* @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem singleJointedArmFromSysId(double kV, double kA) {
if (kV < 0.0) {
throw new IllegalArgumentException("Kv must be greater than or equal to zero.");
}
if (kA <= 0.0) {
throw new IllegalArgumentException("Ka must be greater than zero.");
}
var A = MatBuilder.fill(Nat.N2(), Nat.N2(), 0.0, 1.0, 0.0, -kV / kA);
var B = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, 1.0 / kA);
var C = MatBuilder.fill(Nat.N2(), Nat.N2(), 1.0, 0.0, 0.0, 1.0);
var D = MatBuilder.fill(Nat.N2(), Nat.N1(), 0.0, 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates a differential drive state-space model from physical constants.
*
* The states are [left velocity, right velocity], the inputs are [left voltage, right
* voltage], and the outputs are [left velocity, right velocity].
*
* @param motor The motor (or gearbox) driving the drivetrain.
* @param mass The mass of the robot in kilograms.
* @param r The radius of the wheels in meters.
* @param rb The radius of the base (half of the trackwidth), in meters.
* @param J The moment of inertia of the robot.
* @param gearing Gear ratio from motor to wheel (greater than 1 is a reduction).
* @return Differential drive state-space model.
* @throws IllegalArgumentException if mass <= 0, r <= 0, rb <= 0, J <= 0, or gearing
* <= 0.
*/
public static LinearSystem differentialDriveFromPhysicalConstants(
DCMotor motor, double mass, double r, double rb, double J, double gearing) {
if (mass <= 0.0) {
throw new IllegalArgumentException("mass must be greater than zero.");
}
if (r <= 0.0) {
throw new IllegalArgumentException("r must be greater than zero.");
}
if (rb <= 0.0) {
throw new IllegalArgumentException("rb must be greater than zero.");
}
if (J <= 0.0) {
throw new IllegalArgumentException("J must be greater than zero.");
}
if (gearing <= 0.0) {
throw new IllegalArgumentException("gearing must be greater than zero.");
}
double C1 = -Math.pow(gearing, 2) * motor.Kt / (motor.Kv * motor.R * Math.pow(r, 2));
double C2 = gearing * motor.Kt / (motor.R * r);
var A =
MatBuilder.fill(
Nat.N2(),
Nat.N2(),
(1 / mass + Math.pow(rb, 2) / J) * C1,
(1 / mass - Math.pow(rb, 2) / J) * C1,
(1 / mass - Math.pow(rb, 2) / J) * C1,
(1 / mass + Math.pow(rb, 2) / J) * C1);
var B =
MatBuilder.fill(
Nat.N2(),
Nat.N2(),
(1 / mass + Math.pow(rb, 2) / J) * C2,
(1 / mass - Math.pow(rb, 2) / J) * C2,
(1 / mass - Math.pow(rb, 2) / J) * C2,
(1 / mass + Math.pow(rb, 2) / J) * C2);
var C = MatBuilder.fill(Nat.N2(), Nat.N2(), 1.0, 0.0, 0.0, 1.0);
var D = MatBuilder.fill(Nat.N2(), Nat.N2(), 0.0, 0.0, 0.0, 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates a differential drive state-space model from SysId constants kᵥ and kₐ in both linear
* {(V/(m/s), (V/(m/s²))} and angular {(V/(rad/s), (V/(rad/s²))} cases.
*
* The states are [left velocity, right velocity], the inputs are [left voltage, right
* voltage], and the outputs are [left velocity, right velocity].
*
* @param kvLinear The linear velocity gain in volts per (meters per second).
* @param kaLinear The linear acceleration gain in volts per (meters per second squared).
* @param kvAngular The angular velocity gain in volts per (meters per second).
* @param kaAngular The angular acceleration gain in volts per (meters per second squared).
* @return Differential drive state-space model.
* @throws IllegalArgumentException if kvLinear <= 0, kaLinear <= 0, kvAngular <= 0, or
* kaAngular <= 0.
* @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem differentialDriveFromSysId(
double kvLinear, double kaLinear, double kvAngular, double kaAngular) {
if (kvLinear <= 0.0) {
throw new IllegalArgumentException("Kv,linear must be greater than zero.");
}
if (kaLinear <= 0.0) {
throw new IllegalArgumentException("Ka,linear must be greater than zero.");
}
if (kvAngular <= 0.0) {
throw new IllegalArgumentException("Kv,angular must be greater than zero.");
}
if (kaAngular <= 0.0) {
throw new IllegalArgumentException("Ka,angular must be greater than zero.");
}
double A1 = -0.5 * (kvLinear / kaLinear + kvAngular / kaAngular);
double A2 = -0.5 * (kvLinear / kaLinear - kvAngular / kaAngular);
double B1 = 0.5 / kaLinear + 0.5 / kaAngular;
double B2 = 0.5 / kaLinear - 0.5 / kaAngular;
var A = MatBuilder.fill(Nat.N2(), Nat.N2(), A1, A2, A2, A1);
var B = MatBuilder.fill(Nat.N2(), Nat.N2(), B1, B2, B2, B1);
var C = MatBuilder.fill(Nat.N2(), Nat.N2(), 1.0, 0.0, 0.0, 1.0);
var D = MatBuilder.fill(Nat.N2(), Nat.N2(), 0.0, 0.0, 0.0, 0.0);
return new LinearSystem<>(A, B, C, D);
}
/**
* Creates a differential drive state-space model from SysId constants kᵥ and kₐ in both linear
* {(V/(m/s), (V/(m/s²))} and angular {(V/(rad/s), (V/(rad/s²))} cases.
*
* The states are [left velocity, right velocity], the inputs are [left voltage, right
* voltage], and the outputs are [left velocity, right velocity].
*
* @param kvLinear The linear velocity gain in volts per (meters per second).
* @param kaLinear The linear acceleration gain in volts per (meters per second squared).
* @param kvAngular The angular velocity gain in volts per (radians per second).
* @param kaAngular The angular acceleration gain in volts per (radians per second squared).
* @param trackwidth The distance between the differential drive's left and right wheels, in
* meters.
* @return Differential drive state-space model.
* @throws IllegalArgumentException if kvLinear <= 0, kaLinear <= 0, kvAngular <= 0,
* kaAngular <= 0, or trackwidth <= 0.
* @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem differentialDriveFromSysId(
double kvLinear, double kaLinear, double kvAngular, double kaAngular, double trackwidth) {
if (kvLinear <= 0.0) {
throw new IllegalArgumentException("Kv,linear must be greater than zero.");
}
if (kaLinear <= 0.0) {
throw new IllegalArgumentException("Ka,linear must be greater than zero.");
}
if (kvAngular <= 0.0) {
throw new IllegalArgumentException("Kv,angular must be greater than zero.");
}
if (kaAngular <= 0.0) {
throw new IllegalArgumentException("Ka,angular must be greater than zero.");
}
if (trackwidth <= 0.0) {
throw new IllegalArgumentException("r must be greater than zero.");
}
// We want to find a factor to include in Kv,angular that will convert
// `u = Kv,angular omega` to `u = Kv,angular v`.
//
// v = omega r
// omega = v/r
// omega = 1/r v
// omega = 1/(trackwidth/2) v
// omega = 2/trackwidth v
//
// So multiplying by 2/trackwidth converts the angular gains from V/(rad/s)
// to V/(m/s).
return differentialDriveFromSysId(
kvLinear, kaLinear, kvAngular * 2.0 / trackwidth, kaAngular * 2.0 / trackwidth);
}
}