Low-Dimensional Quadratic Program

Low-Dimensional Quadratic Program

问题形式

  • ,其中,严格正定。

问题形式转化

  • 由于严格正定,根据Cholesky factorization可得,其中是一个下三角矩阵
  • ,原问题转化为,其中
  • 至此,原QP问题转化为了minimum-norm问题,本质是在一个polytope中寻找最靠近原点的点
  • geometry

算法流程

  • geometry
  • 指的是列的维度,当维度降低到1维的时候,就可以直接求结果
  • 投影到后的结果,满足
  • 是原来的原点投影到边界上得到的新的坐标
  • 下图描述了HouseholderProj原理,本质上是将进行降维,在一维空间求得解后升维恢复到原维度
    • geometry
    • 灰色的超平面是算法流程图中的,由于,最优解一定在其表面上。同时,也需要满足超平面集合的约束。
    • 接下来要做的就是把当前的N维空间里的原点(N个0的向量)、超平面集合(若干个组成的集合,其中的向量,是标量),都投影到上。原点投影得到(依然是N维空间下的一个坐标,),投影得到(若干个组成的集合,其中的向量,是标量)。
    • 然后我们在超平面上以为原点,以正交向量为坐标轴新建一个N-1维的坐标系(这样做的意义是:在这个坐标系下的任何一个点,都在平面上)。在这个新坐标系下,我们重新求解距离原点(N-1个0的向量)最近又满足约束的点,这里的是以为原点,以正交向量为基的坐标系下的相对坐标,所以通过公式可以将N-1维空间里得到的解恢复到N维空间。
    • 所以整个流程就是通过不断的投影,将问题降低到1维空间,得到解之后,再逐层恢复到N微空间。
    • 现在有两个问题需要考虑,一是如何求,二是如何求
      • 就是求超平面外一个点在超平面上的投影,公式在上图已经给出了
      • 是超平面上的一组标准正交基,我们已知超平面的表达式为(这里的等同于上面提到的,同理等同于)。所以的法向量是,现在我们构造一组N维的向量,其中表示一个第位为1,其他位都为0的N维向量。我们将的模长缩放到,通过旋转,使得其与重合。将该旋转施加到上,则贴合于超平面。旋转变换的计算方式是通过householder reflection实现的,如下图所示。在实际操作中,的模长缩放可以取负号,选择中绝对值最大的维度,有利于数值稳定。
      • geometry

复杂度分析

  • 是约束的个数

代码

  #include <Eigen/Eigen>
  #include <assert.h>
  #include <cmath>
  #include <iostream>
  #include <tuple>
  #include <typeinfo>
  #include <vector>

  int MaxId(const Eigen::VectorXd &h) {
    int max_id = -1;
    int id = 0;
    double max_ele = std::numeric_limits<double>::lowest();
    while (id < h.rows() - 1) {
      double ele = std::abs(h(id));
      if (ele > max_ele) {
        max_ele = ele;
        max_id = id;
      }
      id++;
    }
    assert(max_id >= 0);
    return max_id;
  }

  double sign(double c) {
    if (c >= 0.0) {
      return 1.0;
    }
    return -1.0;
  }

  struct Constrains {
    Constrains(int dim) {
      A = Eigen::MatrixXd::Zero(0, dim);
      b = Eigen::VectorXd::Zero(0);
    }
    Constrains(const Eigen::MatrixXd &_A, const Eigen::VectorXd &_b) {
      assert(A.rows() == b.rows());
      A = _A;
      b = _b;
    }
    int dim() const { return A.cols(); }
    int size() const { return A.rows(); }
    void insert(const Eigen::VectorXd &_A, const double _b) {
      A.conservativeResize(A.rows() + 1, A.cols());
      A.row(A.rows() - 1) = _A;
      b.conservativeResize(b.rows() + 1);
      b(b.rows() - 1) = _b;
    }
    Eigen::MatrixXd A;
    Eigen::VectorXd b;
  };

  bool OneDimMinNorm(const Constrains &H, Eigen::VectorXd *y) {
    assert(H.A.cols() == 1);
    double low = std::numeric_limits<double>::lowest();
    double up = std::numeric_limits<double>::max();
    for (int i = 0; i < H.A.rows(); ++i) {
      if (H.A(i, 0) > 0) {
        up = std::min(up, H.b(i) / H.A(i, 0));
      } else if (H.A(i, 0) < 0.0) {
        low = std::max(low, H.b(i) / H.A(i, 0));
      }
    }
    if (low > up) {
      return false;
    }
    (*y)(0) = std::min(up, std::max(0.0, low));
    return true;
  }

  std::tuple<Eigen::MatrixXd, Eigen::VectorXd, Constrains>
  HouseholderProj(const Constrains &I, const Eigen::VectorXd &g, double f) {
    int dim = g.rows();
    // calcualte origin v
    Eigen::VectorXd v(dim);
    v = (f * g) / g.dot(g);
    // calcualte orth basis M
    int max_id = MaxId(g);
    Eigen::MatrixXd e = Eigen::MatrixXd::Identity(dim, dim);
    e(max_id, max_id) = (-sign(g(max_id)) * g.norm());
    Eigen::VectorXd u = g - e.col(max_id);
    Eigen::MatrixXd H = Eigen::MatrixXd::Identity(dim, dim) -
                        2.0 * u * u.transpose() / (u.dot(u));
    Eigen::MatrixXd transformed_e = H.transpose() * e;
    double dist = (transformed_e.col(max_id) - g).norm();
    assert(dist <= 0.0000001);
    Eigen::MatrixXd M(dim, dim - 1);
    M << transformed_e.leftCols(max_id),
        transformed_e.rightCols(dim - max_id - 1);
    // calcualte H_dot
    Constrains H_dot(I.A * M, I.b - I.A * v);
    return std::make_tuple(M, v, H_dot);
  }

  bool InConstrain(const Eigen::VectorXd &A, const double b,
                  const Eigen::VectorXd &y) {
    assert(A.rows() == y.rows());
    return A.dot(y) <= b;
  }

  // H: {a.T * x <= b}
  bool LowDimMinNorm(const Constrains &H, Eigen::VectorXd *y) {
    *y = Eigen::VectorXd::Zero(H.dim());
    if (H.size() == 0) {
      return true;
    }
    if (H.dim() == 1) {
      return OneDimMinNorm(H, y);
    }
    Constrains I(H.dim());
    for (int j = 0; j < H.size(); ++j) {
      if (!InConstrain(H.A.row(j), H.b(j), *y)) {
        Eigen::MatrixXd M;
        Eigen::VectorXd v;
        Constrains H_dot(H.dim() - 1);
        std::tie(M, v, H_dot) = HouseholderProj(I, H.A.row(j), H.b(j));
        Eigen::VectorXd y_dot(H.dim() - 1);
        if (!LowDimMinNorm(H_dot, &y_dot)) {
          return false;
        }
        *y = M * y_dot + v;
      }
      I.insert(H.A.row(j), H.b(j));
    }
    return true;
  }

  int main() {
    const int d = 3;
    int m = 7;
    Eigen::Matrix<double, 3, 3> Q;
    Eigen::Matrix<double, 3, 1> c;
    Eigen::Matrix<double, 3, 1> x;        // decision variables
    Eigen::Matrix<double, -1, 3> A(m, 3); // constraint matrix
    Eigen::VectorXd b(m);                 // constraint bound
    Q << 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0;
    c << 1.2, 2.5, -10.0;
    A << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, -0.7, 0.5, 0.0, 0.5, -1.0,
        0.0, 0.0, 0.13, -1.0, 0.1, -3.0, -1.3;
    b << 10.0, 10.0, 10.0, 1.7, -7.1, -3.31, 2.59;
    // 将qp问题转化为min norm问题
    Eigen::LLT<Eigen::Matrix<double, d, d>> llt(Q);
    if (llt.info() != Eigen::Success) {
      std::cout << "infinity\n";
      return 0;
    }
    const Eigen::Matrix<double, -1, d> As =
        llt.matrixU().template solve<Eigen::OnTheRight>(A);
    const Eigen::Matrix<double, d, 1> v = llt.solve(c);
    const Eigen::Matrix<double, -1, 1> bs = A * v + b;

    // 求解min norm问题
    Constrains H(As, bs);
    Eigen::VectorXd z(H.dim());
    if (LowDimMinNorm(H, &z)) {
      llt.matrixU().template solveInPlace<Eigen::OnTheLeft>(z);
      z -= v;
      std::cout << "optimal sol: " << z.transpose() << std::endl;
      std::cout << "minobj: " << 0.5 * (Q * z).dot(z) + c.dot(z) << std::endl;
      std::cout << "cons precision: " << (A * z - b).maxCoeff() << std::endl;
    } else {
      std::cout << "infeasible\n";
    }
    return 0;
  }

Search

    欢迎添加我的微信

    闷骚的程序员

    Table of Contents