347 lines
11 KiB
C++
347 lines
11 KiB
C++
// Copyright (C) 2015 Davis E. King (davis@dlib.net)
|
|
// License: Boost Software License See LICENSE.txt for the full license.
|
|
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
#include <dlib/control.h>
|
|
#include <dlib/optimization.h>
|
|
#include "tester.h"
|
|
|
|
namespace
|
|
{
|
|
using namespace test;
|
|
using namespace std;
|
|
using namespace dlib;
|
|
|
|
logger dlog("test.mpc");
|
|
|
|
template <
|
|
typename EXP1,
|
|
typename EXP2,
|
|
typename T, long NR, long NC, typename MM, typename L
|
|
>
|
|
unsigned long solve_qp_box_using_smo (
|
|
const matrix_exp<EXP1>& _Q,
|
|
const matrix_exp<EXP2>& _b,
|
|
matrix<T,NR,NC,MM,L>& alpha,
|
|
matrix<T,NR,NC,MM,L>& lower,
|
|
matrix<T,NR,NC,MM,L>& upper,
|
|
T eps,
|
|
unsigned long max_iter
|
|
)
|
|
/*!
|
|
ensures
|
|
- solves: 0.5*trans(x)*Q*x + trans(b)*x where x is box constrained.
|
|
!*/
|
|
{
|
|
const_temp_matrix<EXP1> Q(_Q);
|
|
const_temp_matrix<EXP2> b(_b);
|
|
//cout << "IN QP SOLVER" << endl;
|
|
//cout << "max eig: " << max(real_eigenvalues(Q)) << endl;
|
|
//cout << "min eig: " << min(real_eigenvalues(Q)) << endl;
|
|
//cout << "Q: \n" << Q << endl;
|
|
//cout << "b: \n" << b << endl;
|
|
|
|
// make sure requires clause is not broken
|
|
DLIB_ASSERT(Q.nr() == Q.nc() &&
|
|
alpha.size() == lower.size() &&
|
|
alpha.size() == upper.size() &&
|
|
is_col_vector(b) &&
|
|
is_col_vector(alpha) &&
|
|
is_col_vector(lower) &&
|
|
is_col_vector(upper) &&
|
|
b.size() == alpha.size() &&
|
|
b.size() == Q.nr() &&
|
|
alpha.size() > 0 &&
|
|
0 <= min(alpha-lower) &&
|
|
0 <= max(upper-alpha) &&
|
|
eps > 0 &&
|
|
max_iter > 0,
|
|
"\t unsigned long solve_qp_box_using_smo()"
|
|
<< "\n\t Invalid arguments were given to this function"
|
|
<< "\n\t Q.nr(): " << Q.nr()
|
|
<< "\n\t Q.nc(): " << Q.nc()
|
|
<< "\n\t is_col_vector(b): " << is_col_vector(b)
|
|
<< "\n\t is_col_vector(alpha): " << is_col_vector(alpha)
|
|
<< "\n\t is_col_vector(lower): " << is_col_vector(lower)
|
|
<< "\n\t is_col_vector(upper): " << is_col_vector(upper)
|
|
<< "\n\t b.size(): " << b.size()
|
|
<< "\n\t alpha.size(): " << alpha.size()
|
|
<< "\n\t lower.size(): " << lower.size()
|
|
<< "\n\t upper.size(): " << upper.size()
|
|
<< "\n\t Q.nr(): " << Q.nr()
|
|
<< "\n\t min(alpha-lower): " << min(alpha-lower)
|
|
<< "\n\t max(upper-alpha): " << max(upper-alpha)
|
|
<< "\n\t eps: " << eps
|
|
<< "\n\t max_iter: " << max_iter
|
|
);
|
|
|
|
|
|
// Compute f'(alpha) (i.e. the gradient of f(alpha)) for the current alpha.
|
|
matrix<T,NR,NC,MM,L> df = Q*alpha + b;
|
|
matrix<T,NR,NC,MM,L> QQ = reciprocal_max(diag(Q));
|
|
|
|
|
|
unsigned long iter = 0;
|
|
for (; iter < max_iter; ++iter)
|
|
{
|
|
T max_df = 0;
|
|
long best_r =0;
|
|
for (long r = 0; r < Q.nr(); ++r)
|
|
{
|
|
if (alpha(r) <= lower(r) && df(r) > 0)
|
|
;//alpha(r) = lower(r);
|
|
else if (alpha(r) >= upper(r) && df(r) < 0)
|
|
;//alpha(r) = upper(r);
|
|
else if (std::abs(df(r)) > max_df)
|
|
{
|
|
best_r = r;
|
|
max_df = std::abs(df(r));
|
|
}
|
|
}
|
|
|
|
//for (long r = 0; r < Q.nr(); ++r)
|
|
long r = best_r;
|
|
{
|
|
|
|
const T old_alpha = alpha(r);
|
|
alpha(r) = -(df(r)-Q(r,r)*alpha(r))*QQ(r);
|
|
if (alpha(r) < lower(r))
|
|
alpha(r) = lower(r);
|
|
else if (alpha(r) > upper(r))
|
|
alpha(r) = upper(r);
|
|
|
|
const T delta = old_alpha-alpha(r);
|
|
|
|
// Now update the gradient. We will perform the equivalent of: df = Q*alpha + b;
|
|
for(long k = 0; k < df.nr(); ++k)
|
|
df(k) -= Q(r,k)*delta;
|
|
}
|
|
|
|
if (max_df < eps)
|
|
break;
|
|
}
|
|
//cout << "df: \n" << trans(df) << endl;
|
|
//cout << "objective value: " << 0.5*trans(alpha)*Q*alpha + trans(b)*alpha << endl;
|
|
|
|
return iter+1;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
namespace impl_mpc
|
|
{
|
|
template <long N>
|
|
void pack(
|
|
matrix<double,0,1>& out,
|
|
const std::vector<matrix<double,N,1> >& item
|
|
)
|
|
{
|
|
DLIB_CASSERT(item.size() != 0,"");
|
|
out.set_size(item.size()*item[0].size());
|
|
long j = 0;
|
|
for (unsigned long i = 0; i < item.size(); ++i)
|
|
for (long r = 0; r < item[i].size(); ++r)
|
|
out(j++) = item[i](r);
|
|
}
|
|
|
|
template <long N>
|
|
void pack(
|
|
matrix<double,0,1>& out,
|
|
const matrix<double,N,1>& item,
|
|
const long num
|
|
)
|
|
{
|
|
out.set_size(item.size()*num);
|
|
long j = 0;
|
|
for (long r = 0; r < num; ++r)
|
|
for (long i = 0; i < item.size(); ++i)
|
|
out(j++) = item(i);
|
|
}
|
|
|
|
template <long N>
|
|
void unpack(
|
|
std::vector<matrix<double,N,1> >& out,
|
|
const matrix<double,0,1>& item
|
|
)
|
|
{
|
|
DLIB_CASSERT(out.size() != 0,"");
|
|
DLIB_CASSERT((long)out.size()*out[0].size() == item.size(),"");
|
|
long j = 0;
|
|
for (unsigned long i = 0; i < out.size(); ++i)
|
|
for (long r = 0; r < out[i].size(); ++r)
|
|
out[i](r) = item(j++);
|
|
}
|
|
}
|
|
|
|
template <long S, long I>
|
|
unsigned long solve_linear_mpc (
|
|
const matrix<double,S,S>& A,
|
|
const matrix<double,S,I>& B,
|
|
const matrix<double,S,1>& C,
|
|
const matrix<double,S,1>& Q,
|
|
const matrix<double,I,1>& R,
|
|
const matrix<double,I,1>& _lower,
|
|
const matrix<double,I,1>& _upper,
|
|
const std::vector<matrix<double,S,1> >& target,
|
|
const matrix<double,S,1>& initial_state,
|
|
std::vector<matrix<double,I,1> >& controls // input and output
|
|
)
|
|
{
|
|
using namespace impl_mpc;
|
|
DLIB_CASSERT(target.size() == controls.size(),"");
|
|
|
|
matrix<double> K(B.nr()*controls.size(), B.nc()*controls.size());
|
|
matrix<double,0,1> M(B.nr()*controls.size());
|
|
|
|
// compute powers of A: Apow[i] == A^i
|
|
std::vector<matrix<double,S,S> > Apow(controls.size());
|
|
Apow[0] = identity_matrix(A);
|
|
for (unsigned long i = 1; i < Apow.size(); ++i)
|
|
Apow[i] = A*Apow[i-1];
|
|
|
|
// fill in K
|
|
K = 0;
|
|
for (unsigned long r = 0; r < controls.size(); ++r)
|
|
for (unsigned long c = 0; c <= r; ++c)
|
|
set_subm(K,r*B.nr(),c*B.nc(), B.nr(), B.nc()) = Apow[r-c]*B;
|
|
|
|
// fill in M
|
|
set_subm(M,0*A.nr(),0,A.nr(),1) = A*initial_state + C;
|
|
for (unsigned long i = 1; i < controls.size(); ++i)
|
|
set_subm(M,i*A.nr(),0,A.nr(),1) = A*subm(M,(i-1)*A.nr(),0,A.nr(),1) + C;
|
|
|
|
//cout << "M: \n" << M << endl;
|
|
//cout << "K: \n" << K << endl;
|
|
|
|
matrix<double,0,1> t, v, lower, upper;
|
|
pack(t, target);
|
|
pack(v, controls);
|
|
pack(lower, _lower, controls.size());
|
|
pack(upper, _upper, controls.size());
|
|
|
|
|
|
matrix<double> QQ(K.nr(),K.nr()), RR(K.nc(),K.nc());
|
|
QQ = 0;
|
|
RR = 0;
|
|
for (unsigned long c = 0; c < controls.size(); ++c)
|
|
{
|
|
set_subm(QQ,c*Q.nr(),c*Q.nr(),Q.nr(),Q.nr()) = diagm(Q);
|
|
set_subm(RR,c*R.nr(),c*R.nr(),R.nr(),R.nr()) = diagm(R);
|
|
}
|
|
|
|
matrix<double> m1 = trans(K)*QQ*K+RR;
|
|
matrix<double> m2 = trans(K)*QQ*(M-t);
|
|
|
|
|
|
// run the solver...
|
|
unsigned long iter;
|
|
iter = solve_qp_box_using_smo(
|
|
m1,
|
|
m2,
|
|
v,
|
|
lower,
|
|
upper,
|
|
0.00000001,
|
|
100000);
|
|
|
|
//cout << "iterations: " << iter << endl;
|
|
|
|
unpack(controls, v);
|
|
return iter;
|
|
}
|
|
|
|
|
|
|
|
class test_mpc : public tester
|
|
{
|
|
public:
|
|
test_mpc (
|
|
) :
|
|
tester ("test_mpc",
|
|
"Runs tests on the mpc object.")
|
|
{}
|
|
|
|
void perform_test (
|
|
)
|
|
{
|
|
// a basic position + velocity model
|
|
matrix<double,2,2> A;
|
|
A = 1, 1,
|
|
0, 1;
|
|
matrix<double,2,1> B, C;
|
|
B = 0,
|
|
1;
|
|
|
|
C = 0.02,0.1; // no constant bias
|
|
|
|
matrix<double,2,1> Q;
|
|
Q = 2, 0; // only care about getting the position right
|
|
matrix<double,1,1> R, lower, upper;
|
|
R = 1;
|
|
|
|
lower = -0.2;
|
|
upper = 0.2;
|
|
|
|
std::vector<matrix<double,1,1> > controls(30);
|
|
std::vector<matrix<double,2,1> > target(30);
|
|
for (unsigned long i = 0; i < controls.size(); ++i)
|
|
{
|
|
controls[i] = 0;
|
|
target[i] = 0;
|
|
}
|
|
|
|
mpc<2,1,30> solver(A,B,C,Q,R,lower,upper);
|
|
solver.set_epsilon(0.00000001);
|
|
solver.set_max_iterations(10000);
|
|
matrix<double,2,1> initial_state;
|
|
initial_state = 0;
|
|
initial_state(0) = 5;
|
|
for (int i = 0; i < 30; ++i)
|
|
{
|
|
print_spinner();
|
|
matrix<double,1,1> control = solver(initial_state);
|
|
|
|
for (unsigned long i = 1; i < controls.size(); ++i)
|
|
controls[i-1] = controls[i];
|
|
|
|
// Compute the correct control via SMO and make sure it matches.
|
|
solve_linear_mpc(A,B,C,Q,R,lower,upper, target, initial_state, controls);
|
|
dlog << LINFO << "ERROR: " << length(control-controls[0]);
|
|
DLIB_TEST(length(control-controls[0]) < 1e-7);
|
|
|
|
initial_state = A*initial_state + B*control + C;
|
|
//cout << control(0) << "\t" << trans(initial_state);
|
|
}
|
|
|
|
{
|
|
// also just generally test our QP solver.
|
|
matrix<double,20,20> Q = gaussian_randm(20,20,5);
|
|
Q = Q*trans(Q);
|
|
|
|
matrix<double,20,1> b = randm(20,1)-0.5;
|
|
matrix<double,20,1> alpha, lower, upper, alpha2;
|
|
alpha = 0;
|
|
alpha2 = 0;
|
|
lower = -4;
|
|
upper = 3;
|
|
|
|
solve_qp_box_using_smo(Q,b,alpha,lower, upper, 1e-12, 500000);
|
|
solve_qp_box_constrained(Q,b,alpha2,lower, upper, 1e-12, 50000);
|
|
dlog << LINFO << trans(alpha);
|
|
dlog << LINFO << trans(alpha2);
|
|
dlog << LINFO << "objective value: " << 0.5*trans(alpha)*Q*alpha + trans(b)*alpha;
|
|
dlog << LINFO << "objective value2: " << 0.5*trans(alpha2)*Q*alpha + trans(b)*alpha2;
|
|
DLIB_TEST_MSG(max(abs(alpha-alpha2)) < 1e-7, max(abs(alpha-alpha2)));
|
|
}
|
|
}
|
|
} a;
|
|
|
|
}
|
|
|
|
|
|
|
|
|