Ported the polydrivetrain code over to C++.
diff --git a/frc971/control_loops/drivetrain/drivetrain.cc b/frc971/control_loops/drivetrain/drivetrain.cc
index 548113e..122e2d2 100644
--- a/frc971/control_loops/drivetrain/drivetrain.cc
+++ b/frc971/control_loops/drivetrain/drivetrain.cc
@@ -4,11 +4,15 @@
#include <sched.h>
#include <cmath>
#include <memory>
+#include "Eigen/Dense"
#include "aos/common/logging/logging.h"
#include "aos/common/queue.h"
+#include "aos/controls/polytope.h"
+#include "aos/common/commonmath.h"
#include "frc971/control_loops/state_feedback_loop.h"
#include "frc971/control_loops/drivetrain/drivetrain_motor_plant.h"
+#include "frc971/control_loops/drivetrain/polydrivetrain_motor_plant.h"
#include "frc971/control_loops/drivetrain/drivetrain.q.h"
#include "frc971/queues/GyroAngle.q.h"
#include "frc971/queues/Piston.q.h"
@@ -21,6 +25,53 @@
// Width of the robot.
const double width = 22.0 / 100.0 * 2.54;
+Eigen::Matrix<double, 2, 1> CoerceGoal(aos::controls::HPolytope<2> ®ion,
+ const Eigen::Matrix<double, 1, 2> &K,
+ double w,
+ const Eigen::Matrix<double, 2, 1> &R) {
+ if (region.IsInside(R)) {
+ return R;
+ }
+ Eigen::Matrix<double, 2, 1> parallel_vector;
+ Eigen::Matrix<double, 2, 1> perpendicular_vector;
+ perpendicular_vector = K.transpose().normalized();
+ parallel_vector << perpendicular_vector(1, 0), -perpendicular_vector(0, 0);
+
+ aos::controls::HPolytope<1> t_poly(
+ region.H() * parallel_vector,
+ region.k() - region.H() * perpendicular_vector * w);
+
+ Eigen::Matrix<double, 1, Eigen::Dynamic> vertices = t_poly.Vertices();
+ if (vertices.innerSize() > 0) {
+ double min_distance_sqr = 0;
+ Eigen::Matrix<double, 2, 1> closest_point;
+ for (int i = 0; i < vertices.innerSize(); i++) {
+ Eigen::Matrix<double, 2, 1> point;
+ point = parallel_vector * vertices(0, i) + perpendicular_vector * w;
+ const double length = (R - point).squaredNorm();
+ if (i == 0 || length < min_distance_sqr) {
+ closest_point = point;
+ min_distance_sqr = length;
+ }
+ }
+ return closest_point;
+ } else {
+ Eigen::Matrix<double, 2, Eigen::Dynamic> region_vertices =
+ region.Vertices();
+ double min_distance;
+ int closest_i = 0;
+ for (int i = 0; i < region_vertices.outerSize(); i++) {
+ const double length = ::std::abs(
+ (perpendicular_vector.transpose() * (region_vertices.col(i)))(0, 0));
+ if (i == 0 || length < min_distance) {
+ closest_i = i;
+ min_distance = length;
+ }
+ }
+ return region_vertices.col(closest_i);
+ }
+}
+
class DrivetrainMotorsSS {
public:
DrivetrainMotorsSS ()
@@ -86,23 +137,155 @@
bool _control_loop_driving;
};
+class PolyDrivetrain {
+ public:
+ PolyDrivetrain()
+ : U_Poly_((Eigen::Matrix<double, 4, 2>() << /*[[*/ 1, 0 /*]*/,
+ /*[*/ -1, 0 /*]*/,
+ /*[*/ 0, 1 /*]*/,
+ /*[*/ 0, -1 /*]]*/).finished(),
+ (Eigen::Matrix<double, 4, 1>() << /*[[*/ 12 /*]*/,
+ /*[*/ 12 /*]*/,
+ /*[*/ 12 /*]*/,
+ /*[*/ 12 /*]]*/).finished()),
+ loop_(new StateFeedbackLoop<2, 2, 2>(MakeVDrivetrainLoop())) {
+
+ ttrust_ = 1.0;
+
+ wheel_ = 0.0;
+ throttle_ = 0.0;
+ quickturn_ = false;
+ highgear_ = true;
+ }
+ void SetGoal(double wheel, double throttle, bool quickturn, bool highgear) {
+ const double kWheelNonLinearity = 0.1;
+ // Apply a sin function that's scaled to make it feel better.
+ const double angular_range = M_PI_2 * kWheelNonLinearity;
+ wheel_ = sin(angular_range * wheel) / sin(angular_range);
+ wheel_ = sin(angular_range * wheel_) / sin(angular_range);
+ throttle_ = throttle;
+ quickturn_ = quickturn;
+ highgear_ = highgear;
+ if (highgear_) {
+ loop_->set_controller_index(3);
+ } else {
+ loop_->set_controller_index(0);
+ }
+ }
+ double FilterVelocity(double throttle) {
+ const Eigen::Matrix<double, 2, 2> FF =
+ loop_->B().inverse() *
+ (Eigen::Matrix<double, 2, 2>::Identity() - loop_->A());
+
+ constexpr int kHighGearController = 3;
+ const Eigen::Matrix<double, 2, 2> FF_high =
+ loop_->controller(kHighGearController).plant.B.inverse() *
+ (Eigen::Matrix<double, 2, 2>::Identity() -
+ loop_->controller(kHighGearController).plant.A);
+
+ ::Eigen::Matrix<double, 1, 2> FF_sum = FF.colwise().sum();
+ int min_FF_sum_index;
+ const double min_FF_sum = FF_sum.minCoeff(&min_FF_sum_index);
+ const double min_K_sum = loop_->K().col(min_FF_sum_index).sum();
+ const double high_min_FF_sum = FF_high.col(0).sum();
+
+ const double adjusted_ff_voltage = ::aos::Clip(
+ throttle * 12.0 * min_FF_sum / high_min_FF_sum, -12.0, 12.0);
+ return ((adjusted_ff_voltage +
+ ttrust_ * min_K_sum * (loop_->X_hat(0, 0) + loop_->X_hat(1, 0)) /
+ 2.0) /
+ (ttrust_ * min_K_sum + min_FF_sum));
+ }
+
+ void Update() {
+ // FF * X = U (steady state)
+ const Eigen::Matrix<double, 2, 2> FF =
+ loop_->B().inverse() *
+ (Eigen::Matrix<double, 2, 2>::Identity() - loop_->A());
+
+ // Invert the plant to figure out how the velocity filter would have to work
+ // out in order to filter out the forwards negative inertia.
+ // This math assumes that the left and right power and velocity are equals,
+ // and that the plant is the same on the left and right.
+ const double fvel = FilterVelocity(throttle_);
+
+ const double sign_svel = wheel_ * ((fvel > 0.0) ? 1.0 : -1.0);
+ const double svel = ::std::abs(fvel) * wheel_;
+ const double left_velocity = fvel - svel;
+ const double right_velocity = fvel + svel;
+
+ // K * R = w
+ Eigen::Matrix<double,1,2> equality_k;
+ equality_k << 1 + sign_svel, -(1 - sign_svel);
+ const double equality_w = 0.0;
+
+ // Integrate velocity to get the position.
+ // This position is used to get integral control.
+ loop_->R << left_velocity, right_velocity;
+
+ // Construct a constraint on R by manipulating the constraint on U
+ ::aos::controls::HPolytope<2> R_poly = ::aos::controls::HPolytope<2>(
+ U_Poly_.H() * (loop_->K() + FF),
+ U_Poly_.k() + U_Poly_.H() * loop_->K() * loop_->X_hat);
+
+ // Limit R back inside the box.
+ const Eigen::Matrix<double, 2, 1> boxed_R =
+ CoerceGoal(R_poly, equality_k, equality_w, loop_->R);
+
+ const Eigen::Matrix<double, 2, 1> FF_volts = FF * boxed_R;
+ const Eigen::Matrix<double, 2, 1> U_ideal =
+ loop_->K() * (boxed_R - loop_->X_hat) + FF_volts;
+
+ for (int i = 0; i < 2; i++) {
+ loop_->U[i] = ::aos::Clip(U_ideal[i], -12, 12);
+ }
+
+ // TODO(austin): Feed back?
+ loop_->X_hat = loop_->A() * loop_->X_hat + loop_->B() * loop_->U;
+ }
+
+ void SendMotors(Drivetrain::Output *output) {
+ LOG(DEBUG, "left pwm: %f right pwm: %f wheel: %f throttle: %f\n",
+ loop_->U(0, 0), loop_->U(1, 0), wheel_, throttle_);
+ output->left_voltage = loop_->U(0, 0);
+ output->right_voltage = loop_->U(1, 0);
+ if (highgear_) {
+ shifters.MakeWithBuilder().set(false).Send();
+ } else {
+ shifters.MakeWithBuilder().set(true).Send();
+ }
+ }
+
+ private:
+ const ::aos::controls::HPolytope<2> U_Poly_;
+
+ ::std::unique_ptr<StateFeedbackLoop<2, 2, 2>> loop_;
+
+ double ttrust_;
+ double wheel_;
+ double throttle_;
+ bool quickturn_;
+ bool highgear_;
+};
+
+
class DrivetrainMotorsOL {
public:
DrivetrainMotorsOL() {
_old_wheel = 0.0;
- _wheel = 0.0;
- _throttle = 0.0;
- _quickturn = false;
- _highgear = true;
+ wheel_ = 0.0;
+ throttle_ = 0.0;
+ quickturn_ = false;
+ highgear_ = true;
_neg_inertia_accumulator = 0.0;
_left_pwm = 0.0;
_right_pwm = 0.0;
}
void SetGoal(double wheel, double throttle, bool quickturn, bool highgear) {
- _wheel = wheel;
- _throttle = throttle;
- _quickturn = quickturn;
- _highgear = highgear;
+ wheel_ = wheel;
+ throttle_ = throttle;
+ quickturn_ = quickturn;
+ highgear_ = highgear;
_left_pwm = 0.0;
_right_pwm = 0.0;
}
@@ -113,35 +296,35 @@
float linear_power;
double wheel;
- double neg_inertia = _wheel - _old_wheel;
- _old_wheel = _wheel;
+ double neg_inertia = wheel_ - _old_wheel;
+ _old_wheel = wheel_;
double wheelNonLinearity;
- if (_highgear) {
+ if (highgear_) {
wheelNonLinearity = 0.1; // used to be csvReader->TURN_NONLIN_HIGH
// Apply a sin function that's scaled to make it feel better.
const double angular_range = M_PI / 2.0 * wheelNonLinearity;
- wheel = sin(angular_range * _wheel) / sin(angular_range);
+ wheel = sin(angular_range * wheel_) / sin(angular_range);
wheel = sin(angular_range * wheel) / sin(angular_range);
} else {
wheelNonLinearity = 0.2; // used to be csvReader->TURN_NONLIN_LOW
// Apply a sin function that's scaled to make it feel better.
const double angular_range = M_PI / 2.0 * wheelNonLinearity;
- wheel = sin(angular_range * _wheel) / sin(angular_range);
+ wheel = sin(angular_range * wheel_) / sin(angular_range);
wheel = sin(angular_range * wheel) / sin(angular_range);
wheel = sin(angular_range * wheel) / sin(angular_range);
}
static const double kThrottleDeadband = 0.05;
- if (::std::abs(_throttle) < kThrottleDeadband) {
- _throttle = 0;
+ if (::std::abs(throttle_) < kThrottleDeadband) {
+ throttle_ = 0;
} else {
- _throttle = copysign((::std::abs(_throttle) - kThrottleDeadband) /
- (1.0 - kThrottleDeadband), _throttle);
+ throttle_ = copysign((::std::abs(throttle_) - kThrottleDeadband) /
+ (1.0 - kThrottleDeadband), throttle_);
}
double neg_inertia_scalar;
- if (_highgear) {
+ if (highgear_) {
neg_inertia_scalar = 8.0; // used to be csvReader->NEG_INTERTIA_HIGH
sensitivity = 1.22; // used to be csvReader->SENSE_HIGH
} else {
@@ -168,9 +351,9 @@
_neg_inertia_accumulator = 0;
}
- linear_power = _throttle;
+ linear_power = throttle_;
- if (_quickturn) {
+ if (quickturn_) {
double qt_angular_power = wheel;
if (::std::abs(linear_power) < 0.2) {
if (qt_angular_power > 1) qt_angular_power = 1.0;
@@ -179,7 +362,7 @@
qt_angular_power = 0.0;
}
overPower = 1.0;
- if (_highgear) {
+ if (highgear_) {
sensitivity = 1.0;
} else {
sensitivity = 1.0;
@@ -187,7 +370,7 @@
angular_power = wheel;
} else {
overPower = 0.0;
- angular_power = ::std::abs(_throttle) * wheel * sensitivity;
+ angular_power = ::std::abs(throttle_) * wheel * sensitivity;
}
_right_pwm = _left_pwm = linear_power;
@@ -211,12 +394,12 @@
void SendMotors(Drivetrain::Output *output) {
LOG(DEBUG, "left pwm: %f right pwm: %f wheel: %f throttle: %f\n",
- _left_pwm, _right_pwm, _wheel, _throttle);
+ _left_pwm, _right_pwm, wheel_, throttle_);
if (output) {
output->left_voltage = _left_pwm * 12.0;
output->right_voltage = _right_pwm * 12.0;
}
- if (_highgear) {
+ if (highgear_) {
shifters.MakeWithBuilder().set(false).Send();
} else {
shifters.MakeWithBuilder().set(true).Send();
@@ -225,10 +408,10 @@
private:
double _old_wheel;
- double _wheel;
- double _throttle;
- bool _quickturn;
- bool _highgear;
+ double wheel_;
+ double throttle_;
+ bool quickturn_;
+ bool highgear_;
double _neg_inertia_accumulator;
double _left_pwm;
double _right_pwm;
@@ -240,7 +423,7 @@
Drivetrain::Status * /*status*/) {
// TODO(aschuh): These should be members of the class.
static DrivetrainMotorsSS dt_closedloop;
- static DrivetrainMotorsOL dt_openloop;
+ static PolyDrivetrain dt_openloop;
bool bad_pos = false;
if (position == NULL) {
@@ -257,20 +440,19 @@
double left_goal = goal->left_goal;
double right_goal = goal->right_goal;
- dt_closedloop.SetGoal(left_goal, goal->left_velocity_goal,
- right_goal, goal->right_velocity_goal);
+ dt_closedloop.SetGoal(left_goal, goal->left_velocity_goal, right_goal,
+ goal->right_velocity_goal);
if (!bad_pos) {
const double left_encoder = position->left_encoder;
const double right_encoder = position->right_encoder;
if (gyro.FetchLatest()) {
- dt_closedloop.SetPosition(left_encoder, right_encoder,
- gyro->angle, control_loop_driving);
+ dt_closedloop.SetPosition(left_encoder, right_encoder, gyro->angle,
+ control_loop_driving);
} else {
dt_closedloop.SetRawPosition(left_encoder, right_encoder);
}
}
dt_closedloop.Update(position, output == NULL);
- //dt_closedloop.PrintMotors();
dt_openloop.SetGoal(wheel, throttle, quickturn, highgear);
dt_openloop.Update();
if (control_loop_driving) {
diff --git a/frc971/control_loops/drivetrain/drivetrain.gyp b/frc971/control_loops/drivetrain/drivetrain.gyp
index 8e98e4b..89a2321 100644
--- a/frc971/control_loops/drivetrain/drivetrain.gyp
+++ b/frc971/control_loops/drivetrain/drivetrain.gyp
@@ -23,15 +23,18 @@
'sources': [
'drivetrain.cc',
'drivetrain_motor_plant.cc',
+ 'polydrivetrain_motor_plant.cc',
],
'dependencies': [
'drivetrain_loop',
'<(AOS)/common/common.gyp:controls',
'<(DEPTH)/frc971/frc971.gyp:common',
+ '<(DEPTH)/aos/build/externals.gyp:libcdd',
'<(DEPTH)/frc971/control_loops/control_loops.gyp:state_feedback_loop',
'<(DEPTH)/frc971/queues/queues.gyp:queues',
],
'export_dependent_settings': [
+ '<(DEPTH)/aos/build/externals.gyp:libcdd',
'<(DEPTH)/frc971/control_loops/control_loops.gyp:state_feedback_loop',
'<(AOS)/common/common.gyp:controls',
'drivetrain_loop',
diff --git a/frc971/control_loops/drivetrain/drivetrain.h b/frc971/control_loops/drivetrain/drivetrain.h
index 7b07247..63ca76c 100644
--- a/frc971/control_loops/drivetrain/drivetrain.h
+++ b/frc971/control_loops/drivetrain/drivetrain.h
@@ -1,12 +1,20 @@
#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_H_
#define FRC971_CONTROL_LOOPS_DRIVETRAIN_H_
+#include "Eigen/Dense"
+
+#include "aos/controls/polytope.h"
#include "aos/common/control_loop/ControlLoop.h"
#include "frc971/control_loops/drivetrain/drivetrain.q.h"
namespace frc971 {
namespace control_loops {
+Eigen::Matrix<double, 2, 1> CoerceGoal(aos::controls::HPolytope<2> ®ion,
+ const Eigen::Matrix<double, 1, 2> &K,
+ double w,
+ const Eigen::Matrix<double, 2, 1> &R);
+
class DrivetrainLoop
: public aos::control_loops::ControlLoop<control_loops::Drivetrain> {
public:
diff --git a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
index d15de44..43b6443 100644
--- a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
+++ b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
@@ -5,6 +5,8 @@
#include "gtest/gtest.h"
#include "aos/common/queue.h"
#include "aos/common/queue_testutils.h"
+#include "aos/controls/polytope.h"
+
#include "frc971/control_loops/drivetrain/drivetrain.q.h"
#include "frc971/control_loops/drivetrain/drivetrain.h"
#include "frc971/control_loops/state_feedback_loop.h"
@@ -18,6 +20,17 @@
namespace control_loops {
namespace testing {
+class Environment : public ::testing::Environment {
+ public:
+ virtual ~Environment() {}
+ // how to set up the environment.
+ virtual void SetUp() {
+ aos::controls::HPolytope<0>::Init();
+ }
+};
+::testing::Environment* const holder_env =
+ ::testing::AddGlobalTestEnvironment(new Environment);
+
// Class which simulates the drivetrain and sends out queue messages containing the
// position.
@@ -182,6 +195,108 @@
VerifyNearGoal();
}
+::aos::controls::HPolytope<2> MakeBox(double x1_min, double x1_max,
+ double x2_min, double x2_max) {
+ Eigen::Matrix<double, 4, 2> box_H;
+ box_H << /*[[*/ 1.0, 0.0 /*]*/,
+ /*[*/-1.0, 0.0 /*]*/,
+ /*[*/ 0.0, 1.0 /*]*/,
+ /*[*/ 0.0,-1.0 /*]]*/;
+ Eigen::Matrix<double, 4, 1> box_k;
+ box_k << /*[[*/ x1_max /*]*/,
+ /*[*/-x1_min /*]*/,
+ /*[*/ x2_max /*]*/,
+ /*[*/-x2_min /*]]*/;
+ ::aos::controls::HPolytope<2> t_poly(box_H, box_k);
+ return t_poly;
+}
+
+class CoerceGoalTest : public ::testing::Test {
+ public:
+ EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+};
+
+// WHOOOHH!
+TEST_F(CoerceGoalTest, Inside) {
+ ::aos::controls::HPolytope<2> box = MakeBox(1, 2, 1, 2);
+
+ Eigen::Matrix<double, 1, 2> K;
+ K << /*[[*/ 1, -1 /*]]*/;
+
+ Eigen::Matrix<double, 2, 1> R;
+ R << /*[[*/ 1.5, 1.5 /*]]*/;
+
+ Eigen::Matrix<double, 2, 1> output =
+ ::frc971::control_loops::CoerceGoal(box, K, 0, R);
+
+ EXPECT_EQ(R(0, 0), output(0, 0));
+ EXPECT_EQ(R(1, 0), output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, Outside_Inside_Intersect) {
+ ::aos::controls::HPolytope<2> box = MakeBox(1, 2, 1, 2);
+
+ Eigen::Matrix<double, 1, 2> K;
+ K << 1, -1;
+
+ Eigen::Matrix<double, 2, 1> R;
+ R << 5, 5;
+
+ Eigen::Matrix<double, 2, 1> output =
+ ::frc971::control_loops::CoerceGoal(box, K, 0, R);
+
+ EXPECT_EQ(2.0, output(0, 0));
+ EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, Outside_Inside_no_Intersect) {
+ ::aos::controls::HPolytope<2> box = MakeBox(3, 4, 1, 2);
+
+ Eigen::Matrix<double, 1, 2> K;
+ K << 1, -1;
+
+ Eigen::Matrix<double, 2, 1> R;
+ R << 5, 5;
+
+ Eigen::Matrix<double, 2, 1> output =
+ ::frc971::control_loops::CoerceGoal(box, K, 0, R);
+
+ EXPECT_EQ(3.0, output(0, 0));
+ EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, Middle_Of_Edge) {
+ ::aos::controls::HPolytope<2> box = MakeBox(0, 4, 1, 2);
+
+ Eigen::Matrix<double, 1, 2> K;
+ K << -1, 1;
+
+ Eigen::Matrix<double, 2, 1> R;
+ R << 5, 5;
+
+ Eigen::Matrix<double, 2, 1> output =
+ ::frc971::control_loops::CoerceGoal(box, K, 0, R);
+
+ EXPECT_EQ(2.0, output(0, 0));
+ EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, PerpendicularLine) {
+ ::aos::controls::HPolytope<2> box = MakeBox(1, 2, 1, 2);
+
+ Eigen::Matrix<double, 1, 2> K;
+ K << 1, 1;
+
+ Eigen::Matrix<double, 2, 1> R;
+ R << 5, 5;
+
+ Eigen::Matrix<double, 2, 1> output =
+ ::frc971::control_loops::CoerceGoal(box, K, 0, R);
+
+ EXPECT_EQ(1.0, output(0, 0));
+ EXPECT_EQ(1.0, output(1, 0));
+}
+
} // namespace testing
} // namespace control_loops
} // namespace frc971
diff --git a/frc971/control_loops/drivetrain/polydrivetrain_motor_plant.cc b/frc971/control_loops/drivetrain/polydrivetrain_motor_plant.cc
new file mode 100644
index 0000000..31a029f
--- /dev/null
+++ b/frc971/control_loops/drivetrain/polydrivetrain_motor_plant.cc
@@ -0,0 +1,125 @@
+#include "frc971/control_loops/drivetrain/polydrivetrain_motor_plant.h"
+
+#include <vector>
+
+#include "frc971/control_loops/state_feedback_loop.h"
+
+namespace frc971 {
+namespace control_loops {
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainLowLowPlantCoefficients() {
+ Eigen::Matrix<double, 2, 2> A;
+ A << 0.899177606502, 0.000686937184856, 0.000686937184856, 0.899177606502;
+ Eigen::Matrix<double, 2, 2> B;
+ B << 0.0186835844877, -0.000127297602107, -0.000127297602107, 0.0186835844877;
+ Eigen::Matrix<double, 2, 2> C;
+ C << 1.0, 0.0, 0.0, 1.0;
+ Eigen::Matrix<double, 2, 2> D;
+ D << 0.0, 0.0, 0.0, 0.0;
+ Eigen::Matrix<double, 2, 1> U_max;
+ U_max << 12.0, 12.0;
+ Eigen::Matrix<double, 2, 1> U_min;
+ U_min << -12.0, -12.0;
+ return StateFeedbackPlantCoefficients<2, 2, 2>(A, B, C, D, U_max, U_min);
+}
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainLowHighPlantCoefficients() {
+ Eigen::Matrix<double, 2, 2> A;
+ A << 0.89917740051, 0.000149762601927, 0.000716637450627, 0.978035563679;
+ Eigen::Matrix<double, 2, 2> B;
+ B << 0.0186836226605, -6.07092175404e-05, -0.00013280141337, 0.0089037164526;
+ Eigen::Matrix<double, 2, 2> C;
+ C << 1.0, 0.0, 0.0, 1.0;
+ Eigen::Matrix<double, 2, 2> D;
+ D << 0.0, 0.0, 0.0, 0.0;
+ Eigen::Matrix<double, 2, 1> U_max;
+ U_max << 12.0, 12.0;
+ Eigen::Matrix<double, 2, 1> U_min;
+ U_min << -12.0, -12.0;
+ return StateFeedbackPlantCoefficients<2, 2, 2>(A, B, C, D, U_max, U_min);
+}
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainHighLowPlantCoefficients() {
+ Eigen::Matrix<double, 2, 2> A;
+ A << 0.978035563679, 0.000716637450627, 0.000149762601927, 0.89917740051;
+ Eigen::Matrix<double, 2, 2> B;
+ B << 0.0089037164526, -0.00013280141337, -6.07092175404e-05, 0.0186836226605;
+ Eigen::Matrix<double, 2, 2> C;
+ C << 1.0, 0.0, 0.0, 1.0;
+ Eigen::Matrix<double, 2, 2> D;
+ D << 0.0, 0.0, 0.0, 0.0;
+ Eigen::Matrix<double, 2, 1> U_max;
+ U_max << 12.0, 12.0;
+ Eigen::Matrix<double, 2, 1> U_min;
+ U_min << -12.0, -12.0;
+ return StateFeedbackPlantCoefficients<2, 2, 2>(A, B, C, D, U_max, U_min);
+}
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainHighHighPlantCoefficients() {
+ Eigen::Matrix<double, 2, 2> A;
+ A << 0.978035518136, 0.000156145735499, 0.000156145735499, 0.978035518136;
+ Eigen::Matrix<double, 2, 2> B;
+ B << 0.0089037349145, -6.32967463335e-05, -6.32967463335e-05, 0.0089037349145;
+ Eigen::Matrix<double, 2, 2> C;
+ C << 1.0, 0.0, 0.0, 1.0;
+ Eigen::Matrix<double, 2, 2> D;
+ D << 0.0, 0.0, 0.0, 0.0;
+ Eigen::Matrix<double, 2, 1> U_max;
+ U_max << 12.0, 12.0;
+ Eigen::Matrix<double, 2, 1> U_min;
+ U_min << -12.0, -12.0;
+ return StateFeedbackPlantCoefficients<2, 2, 2>(A, B, C, D, U_max, U_min);
+}
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainLowLowController() {
+ Eigen::Matrix<double, 2, 2> L;
+ L << 0.879177606502, 0.000686937184856, 0.000686937184856, 0.879177606502;
+ Eigen::Matrix<double, 2, 2> K;
+ K << 32.0714744818, 0.255280724401, 0.255280724401, 32.0714744818;
+ return StateFeedbackController<2, 2, 2>(L, K, MakeVelocityDrivetrainLowLowPlantCoefficients());
+}
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainLowHighController() {
+ Eigen::Matrix<double, 2, 2> L;
+ L << 0.879178111554, 0.000716636558747, 0.000716636558747, 0.958034852635;
+ Eigen::Matrix<double, 2, 2> K;
+ K << 32.0714744826, 0.255470827831, 0.558842435881, 76.1557821586;
+ return StateFeedbackController<2, 2, 2>(L, K, MakeVelocityDrivetrainLowHighPlantCoefficients());
+}
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainHighLowController() {
+ Eigen::Matrix<double, 2, 2> L;
+ L << 0.958040379369, 0.000149803514919, 0.000149803514919, 0.87917258482;
+ Eigen::Matrix<double, 2, 2> K;
+ K << 76.1557821586, 0.558842435881, 0.255470827831, 32.0714744826;
+ return StateFeedbackController<2, 2, 2>(L, K, MakeVelocityDrivetrainHighLowPlantCoefficients());
+}
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainHighHighController() {
+ Eigen::Matrix<double, 2, 2> L;
+ L << 0.958035518136, 0.000156145735499, 0.000156145735499, 0.958035518136;
+ Eigen::Matrix<double, 2, 2> K;
+ K << 76.1557821586, 0.558929371597, 0.558929371597, 76.1557821586;
+ return StateFeedbackController<2, 2, 2>(L, K, MakeVelocityDrivetrainHighHighPlantCoefficients());
+}
+
+StateFeedbackPlant<2, 2, 2> MakeVDrivetrainPlant() {
+ ::std::vector<StateFeedbackPlantCoefficients<2, 2, 2> *> plants(4);
+ plants[0] = new StateFeedbackPlantCoefficients<2, 2, 2>(MakeVelocityDrivetrainLowLowPlantCoefficients());
+ plants[1] = new StateFeedbackPlantCoefficients<2, 2, 2>(MakeVelocityDrivetrainLowHighPlantCoefficients());
+ plants[2] = new StateFeedbackPlantCoefficients<2, 2, 2>(MakeVelocityDrivetrainHighLowPlantCoefficients());
+ plants[3] = new StateFeedbackPlantCoefficients<2, 2, 2>(MakeVelocityDrivetrainHighHighPlantCoefficients());
+ return StateFeedbackPlant<2, 2, 2>(plants);
+}
+
+StateFeedbackLoop<2, 2, 2> MakeVDrivetrainLoop() {
+ ::std::vector<StateFeedbackController<2, 2, 2> *> controllers(4);
+ controllers[0] = new StateFeedbackController<2, 2, 2>(MakeVelocityDrivetrainLowLowController());
+ controllers[1] = new StateFeedbackController<2, 2, 2>(MakeVelocityDrivetrainLowHighController());
+ controllers[2] = new StateFeedbackController<2, 2, 2>(MakeVelocityDrivetrainHighLowController());
+ controllers[3] = new StateFeedbackController<2, 2, 2>(MakeVelocityDrivetrainHighHighController());
+ return StateFeedbackLoop<2, 2, 2>(controllers);
+}
+
+} // namespace control_loops
+} // namespace frc971
diff --git a/frc971/control_loops/drivetrain/polydrivetrain_motor_plant.h b/frc971/control_loops/drivetrain/polydrivetrain_motor_plant.h
new file mode 100644
index 0000000..99b113e
--- /dev/null
+++ b/frc971/control_loops/drivetrain/polydrivetrain_motor_plant.h
@@ -0,0 +1,32 @@
+#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_POLYDRIVETRAIN_MOTOR_PLANT_H_
+#define FRC971_CONTROL_LOOPS_DRIVETRAIN_POLYDRIVETRAIN_MOTOR_PLANT_H_
+
+#include "frc971/control_loops/state_feedback_loop.h"
+
+namespace frc971 {
+namespace control_loops {
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainLowLowPlantCoefficients();
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainLowLowController();
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainLowHighPlantCoefficients();
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainLowHighController();
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainHighLowPlantCoefficients();
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainHighLowController();
+
+StateFeedbackPlantCoefficients<2, 2, 2> MakeVelocityDrivetrainHighHighPlantCoefficients();
+
+StateFeedbackController<2, 2, 2> MakeVelocityDrivetrainHighHighController();
+
+StateFeedbackPlant<2, 2, 2> MakeVDrivetrainPlant();
+
+StateFeedbackLoop<2, 2, 2> MakeVDrivetrainLoop();
+
+} // namespace control_loops
+} // namespace frc971
+
+#endif // FRC971_CONTROL_LOOPS_DRIVETRAIN_POLYDRIVETRAIN_MOTOR_PLANT_H_