This is a repository for Minimum Snap Model
. If you want to know more about Minimum Snap Model
, you can read this paper.
I write a blog about this paper in Chinese. You can read this blog to understand this model.
I write two optimization methods for this model.
In this branch, we want to optimize the polynomial coefficients to minimize the loss function. The loss function is
$$
L(p) = \sum_{i=1}^mJ_i^2(T_i)
$$
where
For the loss of
Note that in the minimum snap model, we convert this problem to a quadratic convex optimization problem. The format should be
$$
\min_{\substack{p}} L(p) = p^T Q p + f^T p
$$
In our problem, there is no
You can calculate the scale_mat
, we can calculate it by
$$
scale_mat_{(n-3)\times(n-3)} = \left[\begin{array}{ccc}
& 2n-7 & 2n-6 & \dots & n-3 \
& 2n-6 & 2n-5 & \dots & n-4 \
& \vdots & \vdots & \dots & \vdots \
& n-3 & n-4 & \dots & 1 \
\end{array}\right]
$$
Using scale_mat
, we can easily calculate T
matrix, which is
$$
T = T_i^{scale_mat}
$$
For the numerator coef_mat
, we can decompose it to the multiplication of two vectors, which is
$$
coef_mat = \left[\begin{array}{ccc}
C(n) &C(n-1)& C(n-2)& \dots& C(4)
\end{array}\right]^T\left[\begin{array}{ccc}
C(n) &C(n-1)& C(n-2)& \dots& C(4)
\end{array}\right]
$$
Here we define a function scale_mat
, coef_mat
and T
, we can easily calculate the
def initialize(self):
# calculate the coef_mat and scale_mat
coef_func = lambda x: x * (x - 1) * (x - 2) * (x - 3)
coef_mat = np.zeros((self.n_order + 1, self.n_order + 1))
scale_mat = np.ones((self.n_order + 1, self.n_order + 1))
coef_vec1 = np.array(
[coef_func(i) for i in range(self.n_order, 3, -1)]).reshape(-1, 1)
coef_vec2 = np.array(
[coef_func(i) for i in range(self.n_order, 3, -1)]).reshape(1, -1)
coef_mat[:self.n_order - 3, :self.n_order - 3] = coef_vec1 @ coef_vec2
scale_mat_list = []
for _ in range(self.n_order - 3):
if len(scale_mat_list) == 0:
scale_mat_list.append(
np.array(list(range(2 * self.n_order - 7, self.n_order - 4, -1))))
else:
scale_mat_list.append(scale_mat_list[-1] - 1)
scale_mat[:self.n_order - 3, :self.n_order - 3] = np.array(scale_mat_list)
return coef_mat, scale_mat
def get_quadratic_matrix(self):
# calculate quadratic matrix
Q = []
for i in range(self.n_seg):
seg_time = self.seg_time[i]
T_mat = seg_time**self.scale_mat
seg_Q = self.coef_mat * T_mat / self.scale_mat
Q.append(seg_Q)
return block_diag(*Q)
For minimum snap model, we need to add some constraints. But before it, I would like to calculate a matrix
And we know that
$$
\begin{aligned}
p & = \sum_{i=0}^np_it^i \
v &= \sum_{i=1}^nip_it^{i-1} \
a &= \sum_{i=2}^ni(i-1)p_it^{i-2} \
j &= \sum_{i=3}^ni(i-1)(i-2)p_it^{i-3} \
\end{aligned}
$$
Therefore,
def get_motion_coef(self, t):
motion_coef = []
poly_coef = np.ones(self.n_order + 1)
t_power = np.arange(self.n_order, -1, -1)
t_value = t**t_power
motion_coef.append(poly_coef * t_value)
for i in range(3):
poly_coef = np.poly1d(poly_coef).deriv().coeffs
poly_coef_pad = np.hstack([poly_coef, np.array([0, ] * (i + 1))])
t_power -= 1
# avoid 0^i where i < 0
t_power[t_power < 0] = 0
t_value = t**t_power
motion_coef.append(poly_coef_pad * t_value)
return np.array(motion_coef)
For the start point, we define $$ \left[\begin{array}{cccc} p& v& a &j \end{array}\right]^T = \left[\begin{array}{cccc} p_{start}& 0 & 0 & 0 \end{array}\right]^T $$ The code is
n_var = self.n_seg * (self.n_order + 1)
Aeq_start = np.zeros((4, n_var))
Aeq_start[:4, :self.n_poly] = self.get_motion_coef(0)
beq_start = self.start_cond
For each segment, we have n_poly = n_order + 1
variables, so we have n_var=n_seg * (n_order + 1)
variables totally. And the first n_poly
variables are coresponding to the first segment.
For the end point, we define $$ \left[\begin{array}{cccc} p& v& a &j \end{array}\right]^T = \left[\begin{array}{cccc} p_{end}& 0 & 0 & 0 \end{array}\right]^T $$ The code is
Aeq_end = np.zeros((4, n_var))
Aeq_end[:4, -self.n_poly:] = self.get_motion_coef(self.seg_time[-1])
beq_end = self.end_cond
Aeq.append(Aeq_end)
beq.append(beq_end)
For the middle points, we should also add position constraints.
Aeq_pos = np.zeros((self.n_seg - 1, n_var))
beq_pos = np.zeros(self.n_seg - 1)
for i in range(self.n_seg - 1):
Aeq_pos[i, self.n_poly * i:self.n_poly * (i + 1)] = self.get_motion_coef(self.seg_time[i])[0]
beq_pos[i] = self.waypoints[i + 1]
Aeq.append(Aeq_pos)
beq.append(beq_pos)
For the waypoints, we should add continuity constraints, which is
$$
\begin{aligned}
\left[\begin{array}{cccc}
p_{i^-}& v_{i^-}& a_{i^-} &j_{i^-}
\end{array}\right]^T & = \left[\begin{array}{cccc}
p_{i^+}& v_{i^+}& a_{i^+} &j_{i^+}
\end{array}\right]^T \
\end{aligned}
$$
And we can use
Aeq_continue = np.zeros((4 * (self.n_seg - 1), n_var))
beq_continue = np.zeros(4 * (self.n_seg - 1))
for i in range(self.n_seg - 1):
# the end of last segment
last_coef_mat_end = self.get_motion_coef(self.seg_time[i])
# the start of the next segment
next_coef_mat_start = self.get_motion_coef(0)
Aeq_continue[4 * i:4 * (i + 1),
self.n_poly * i:self.n_poly * (i + 1)] = last_coef_mat_end
Aeq_continue[4 * i:4 * (i + 1),
self.n_poly * (i + 1):self.n_poly * (i + 2)] = -next_coef_mat_start
pip install -r requirements
python main.py -n 5 -alloc proportion --show
The result is as follows