blob: 18476827e6bf132de29ded17ecc4ec6c5a2e11a7 [file] [log] [blame]
James Kuszmaulfb0e0ae2014-03-25 07:04:47 -07001#ifndef FRC971_CONTROL_LOOPS_COERCE_GOAL_H_
2#define FRC971_CONTROL_LOOPS_COERCE_GOAL_H_
3
4#include "Eigen/Dense"
James Kuszmaul61750662021-06-21 21:32:33 -07005#include "frc971/control_loops/polytope.h"
James Kuszmaulfb0e0ae2014-03-25 07:04:47 -07006
7namespace frc971 {
8namespace control_loops {
9
Austin Schuhbcce26a2018-03-26 23:41:24 -070010template <typename Scalar = double>
11Eigen::Matrix<Scalar, 2, 1> DoCoerceGoal(
James Kuszmaul61750662021-06-21 21:32:33 -070012 const frc971::controls::HVPolytope<2, 4, 4, Scalar> &region,
Austin Schuhbcce26a2018-03-26 23:41:24 -070013 const Eigen::Matrix<Scalar, 1, 2> &K, Scalar w,
14 const Eigen::Matrix<Scalar, 2, 1> &R, bool *is_inside);
Brian Silverman6dd2c532014-03-29 23:34:39 -070015
James Kuszmaulfb0e0ae2014-03-25 07:04:47 -070016// Intersects a line with a region, and finds the closest point to R.
17// Finds a point that is closest to R inside the region, and on the line
18// defined by K X = w. If it is not possible to find a point on the line,
19// finds a point that is inside the region and closest to the line.
Austin Schuhbcce26a2018-03-26 23:41:24 -070020template <typename Scalar = double>
21static inline Eigen::Matrix<Scalar, 2, 1> CoerceGoal(
James Kuszmaul61750662021-06-21 21:32:33 -070022 const frc971::controls::HVPolytope<2, 4, 4, Scalar> &region,
Austin Schuhbcce26a2018-03-26 23:41:24 -070023 const Eigen::Matrix<Scalar, 1, 2> &K, Scalar w,
24 const Eigen::Matrix<Scalar, 2, 1> &R) {
Brian Silverman6dd2c532014-03-29 23:34:39 -070025 return DoCoerceGoal(region, K, w, R, nullptr);
26}
James Kuszmaulfb0e0ae2014-03-25 07:04:47 -070027
Austin Schuhbcce26a2018-03-26 23:41:24 -070028template <typename Scalar>
29Eigen::Matrix<Scalar, 2, 1> DoCoerceGoal(
James Kuszmaul61750662021-06-21 21:32:33 -070030 const frc971::controls::HVPolytope<2, 4, 4, Scalar> &region,
Austin Schuhbcce26a2018-03-26 23:41:24 -070031 const Eigen::Matrix<Scalar, 1, 2> &K, Scalar w,
32 const Eigen::Matrix<Scalar, 2, 1> &R, bool *is_inside) {
33 if (region.IsInside(R)) {
34 if (is_inside) *is_inside = true;
35 return R;
36 }
James Kuszmaul0ee1fce2020-01-11 16:59:21 -080037 const Scalar norm_K = K.norm();
Austin Schuhbcce26a2018-03-26 23:41:24 -070038 Eigen::Matrix<Scalar, 2, 1> parallel_vector;
39 Eigen::Matrix<Scalar, 2, 1> perpendicular_vector;
James Kuszmaul0ee1fce2020-01-11 16:59:21 -080040 // Calculate a vector that is perpendicular to the line defined by K * x = w.
41 perpendicular_vector = K.transpose() / norm_K;
42 // Calculate a vector that is parallel to the line defined by K * x = w.
Austin Schuhbcce26a2018-03-26 23:41:24 -070043 parallel_vector << perpendicular_vector(1, 0), -perpendicular_vector(0, 0);
44
James Kuszmaul0ee1fce2020-01-11 16:59:21 -080045 // Calculate the location along the K x = w line where each boundary of the
46 // polytope would intersect.
47 // I.e., we want to calculate the distance along the K x = w line, as
48 // represented by
49 // parallel_vector * dist + perpendicular_vector * w / norm(K),
50 // such that it intersects with H_i * x = k_i.
51 // This gives us H_i * (parallel * dist + perp * w / norm(K)) = k_i.
52 // dist = (k_i - H_i * perp * w / norm(K)) / (H_i * parallel)
53 // projectedh is the numerator, projectedk is the denominator.
54 // The case where H_i * parallel is zero indicates a scenario where the given
55 // boundary of the polytope is parallel to the line, and so there is no
56 // meaningful value of dist to return.
57 // Note that the sign of projectedh will also indicate whether the distance is
58 // an upper or lower bound. If since valid points satisfy H_i * x < k_i, then
59 // if H_i * parallel is less than zero, then dist will be a lower bound and
60 // if it is greater than zero, then dist will be an upper bound.
61 const Eigen::Matrix<Scalar, 4, 1> projectedh =
62 region.static_H() * parallel_vector;
63 const Eigen::Matrix<Scalar, 4, 1> projectedk =
64 region.static_k() - region.static_H() * perpendicular_vector * w / norm_K;
Austin Schuhbcce26a2018-03-26 23:41:24 -070065
66 Scalar min_boundary = ::std::numeric_limits<Scalar>::lowest();
67 Scalar max_boundary = ::std::numeric_limits<Scalar>::max();
68 for (int i = 0; i < 4; ++i) {
69 if (projectedh(i, 0) > 0) {
70 max_boundary =
71 ::std::min(max_boundary, projectedk(i, 0) / projectedh(i, 0));
Lee Mracek65686142020-01-10 22:21:39 -050072 } else if (projectedh(i, 0) != 0) {
Austin Schuhbcce26a2018-03-26 23:41:24 -070073 min_boundary =
74 ::std::max(min_boundary, projectedk(i, 0) / projectedh(i, 0));
75 }
76 }
77
78 Eigen::Matrix<Scalar, 1, 2> vertices;
79 vertices << max_boundary, min_boundary;
80
81 if (max_boundary > min_boundary) {
James Kuszmaul0ee1fce2020-01-11 16:59:21 -080082 // The line goes through the region (if the line just intersects a single
83 // vertex, then we fall through to the next clause), but R is not
84 // inside the region. The returned point will be one of the two points where
85 // the line intersects the edge of the region.
Austin Schuhbcce26a2018-03-26 23:41:24 -070086 Scalar min_distance_sqr = 0;
87 Eigen::Matrix<Scalar, 2, 1> closest_point;
88 for (int i = 0; i < vertices.innerSize(); i++) {
89 Eigen::Matrix<Scalar, 2, 1> point;
James Kuszmaul0ee1fce2020-01-11 16:59:21 -080090 point =
91 parallel_vector * vertices(0, i) + perpendicular_vector * w / norm_K;
Austin Schuhbcce26a2018-03-26 23:41:24 -070092 const Scalar length = (R - point).squaredNorm();
93 if (i == 0 || length < min_distance_sqr) {
94 closest_point = point;
95 min_distance_sqr = length;
96 }
97 }
98 if (is_inside) *is_inside = true;
99 return closest_point;
100 } else {
James Kuszmaul0ee1fce2020-01-11 16:59:21 -0800101 // The line does not pass through the region; identify the vertex closest to
102 // the line.
Austin Schuhbcce26a2018-03-26 23:41:24 -0700103 Eigen::Matrix<Scalar, 2, 4> region_vertices = region.StaticVertices();
104#ifdef __linux__
Alex Perrycb7da4b2019-08-28 19:35:56 -0700105 CHECK_GT(reinterpret_cast<ssize_t>(region_vertices.outerSize()), 0);
Austin Schuhbcce26a2018-03-26 23:41:24 -0700106#else
107 assert(region_vertices.outerSize() > 0);
108#endif
109 Scalar min_distance = INFINITY;
110 int closest_i = 0;
111 for (int i = 0; i < region_vertices.outerSize(); i++) {
James Kuszmaul0ee1fce2020-01-11 16:59:21 -0800112 // Calculate the distance of the vertex from the line. The closest vertex
113 // will be the one to return.
Austin Schuhbcce26a2018-03-26 23:41:24 -0700114 const Scalar length = ::std::abs(
James Kuszmaul0ee1fce2020-01-11 16:59:21 -0800115 (perpendicular_vector.transpose() * (region_vertices.col(i)))(0, 0) -
116 w);
Austin Schuhbcce26a2018-03-26 23:41:24 -0700117 if (i == 0 || length < min_distance) {
118 closest_i = i;
119 min_distance = length;
120 }
121 }
122 if (is_inside) *is_inside = false;
123 return (Eigen::Matrix<Scalar, 2, 1>() << region_vertices(0, closest_i),
124 region_vertices(1, closest_i))
125 .finished();
126 }
127}
128
James Kuszmaulfb0e0ae2014-03-25 07:04:47 -0700129} // namespace control_loops
130} // namespace frc971
131
132#endif // FRC971_CONTROL_LOOPS_COERCE_GOAL_H_