Skip to content

simplex.hpp

SECTIONOptimization INCLUDEnoya/simplex.hpp

求线性规划 \(\max c\mathbin{\cdot}x\),约束 \(Ax\le b\)\(x\ge0\),并区分最优、无解和无界。

Complexity: Time: Exponential worst case; typically polynomial pivot work per iteration. Space: O(rows * columns).

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include

/// @complexity Time: Exponential worst case; typically polynomial pivot work per iteration.
/// Space: O(rows * columns).

#include <algorithm>
#include <cassert>
#include <cmath>
#include <limits>
#include <utility>
#include <vector>

namespace noya {

enum class linear_program_status { optimal, infeasible, unbounded };

struct linear_program_result {
  linear_program_status sta = linear_program_status::infeasible;
  long double obj = 0;
  std::vector<long double> sol;
};

namespace simplex_internal {

struct tableau {
  static constexpr long double eps = 1e-12L;
  // m constraints, n variables.
  int m;
  int n;
  std::vector<int> bas;
  std::vector<int> nb;
  std::vector<std::vector<long double>> val;

  tableau(const std::vector<std::vector<long double>> &a,
          const std::vector<long double> &b, const std::vector<long double> &c)
      : m(int(b.size())), n(int(c.size())), bas(m), nb(n + 1),
        val(m + 2, std::vector<long double>(n + 2)) {
    for (int row = 0; row < m; row++) {
      for (int col = 0; col < n; col++) {
        val[row][col] = a[row][col];
      }
      bas[row] = n + row;
      val[row][n] = -1;
      val[row][n + 1] = b[row];
    }
    for (int col = 0; col < n; col++) {
      nb[col] = col;
      val[m][col] = -c[col];
    }
    nb[n] = -1;
    val[m + 1][n] = 1;
  }

  void pivot(int lr, int ec) {
    long double inv = 1 / val[lr][ec];
    for (int row = 0; row < m + 2; row++) {
      if (row == lr) {
        continue;
      }
      for (int col = 0; col < n + 2; col++) {
        if (col != ec) {
          val[row][col] -= val[lr][col] * val[row][ec] * inv;
        }
      }
    }
    for (int col = 0; col < n + 2; col++) {
      if (col != ec) {
        val[lr][col] *= inv;
      }
    }
    for (int row = 0; row < m + 2; row++) {
      if (row != lr) {
        val[row][ec] *= -inv;
      }
    }
    val[lr][ec] = inv;
    std::swap(bas[lr], nb[ec]);
  }

  bool optimize(int ph) {
    int oi = ph == 1 ? m + 1 : m;
    while (true) {
      int ent = -1;
      for (int col = 0; col <= n; col++) {
        if (ph == 2 && nb[col] == -1) {
          continue;
        }
        if (ent == -1 || val[oi][col] < val[oi][ent] - eps ||
            (std::abs(val[oi][col] - val[oi][ent]) <= eps &&
             nb[col] < nb[ent])) {
          ent = col;
        }
      }
      if (ent == -1) {
        return true;
      }
      if (val[oi][ent] >= -eps) {
        return true;
      }
      int lea = -1;
      for (int row = 0; row < m; row++) {
        if (val[row][ent] <= eps) {
          continue;
        }
        if (lea == -1) {
          lea = row;
          continue;
        }
        long double lhs = val[row][n + 1] / val[row][ent];
        long double rhs = val[lea][n + 1] / val[lea][ent];
        if (lhs < rhs - eps ||
            (std::abs(lhs - rhs) <= eps && bas[row] < bas[lea])) {
          lea = row;
        }
      }
      if (lea == -1) {
        return false;
      }
      pivot(lea, ent);
    }
  }

  linear_program_result solve() {
    int mi = 0;
    for (int row = 1; row < m; row++) {
      if (val[row][n + 1] < val[mi][n + 1]) {
        mi = row;
      }
    }
    if (m > 0 && val[mi][n + 1] < -eps) {
      pivot(mi, n);
      if (!optimize(1) || val[m + 1][n + 1] < -eps) {
        return {linear_program_status::infeasible, 0, {}};
      }
      if (std::abs(val[m + 1][n + 1]) > eps) {
        return {linear_program_status::infeasible, 0, {}};
      }
      for (int row = 0; row < m; row++) {
        if (bas[row] != -1) {
          continue;
        }
        int ent = 0;
        for (int col = 1; col <= n; col++) {
          if (std::abs(val[row][col]) > std::abs(val[row][ent]) + eps ||
              (std::abs(std::abs(val[row][col]) - std::abs(val[row][ent])) <=
                   eps &&
               nb[col] < nb[ent])) {
            ent = col;
          }
        }
        if (std::abs(val[row][ent]) > eps) {
          pivot(row, ent);
        }
      }
    }
    if (!optimize(2)) {
      return {linear_program_status::unbounded,
              std::numeric_limits<long double>::infinity(),
              {}};
    }
    std::vector<long double> sol(n);
    for (int row = 0; row < m; row++) {
      if (bas[row] < n) {
        sol[bas[row]] = val[row][n + 1];
      }
    }
    return {linear_program_status::optimal, val[m][n + 1], std::move(sol)};
  }
};

} // namespace simplex_internal

/// @brief Maximize c*x subject to A*x <= b and x >= 0 with a two-phase
/// simplex tableau; reports optimal, infeasible, or unbounded.
inline linear_program_result
simplex(const std::vector<std::vector<long double>> &a,
        const std::vector<long double> &b, const std::vector<long double> &c) {
  assert(a.size() == b.size());
  for (const auto &row : a) {
    assert(row.size() == c.size());
  }
  return simplex_internal::tableau(a, b, c).solve();
}

} // namespace noya
#ifndef NOYA_SIMPLEX_HPP
#define NOYA_SIMPLEX_HPP 1

/// @complexity Time: Exponential worst case; typically polynomial pivot work per iteration.
/// Space: O(rows * columns).

#include <algorithm>
#include <cassert>
#include <cmath>
#include <limits>
#include <utility>
#include <vector>

namespace noya {

enum class linear_program_status { optimal, infeasible, unbounded };

struct linear_program_result {
  linear_program_status sta = linear_program_status::infeasible;
  long double obj = 0;
  std::vector<long double> sol;
};

namespace simplex_internal {

struct tableau {
  static constexpr long double eps = 1e-12L;
  // m constraints, n variables.
  int m;
  int n;
  std::vector<int> bas;
  std::vector<int> nb;
  std::vector<std::vector<long double>> val;

  tableau(const std::vector<std::vector<long double>> &a,
          const std::vector<long double> &b, const std::vector<long double> &c)
      : m(int(b.size())), n(int(c.size())), bas(m), nb(n + 1),
        val(m + 2, std::vector<long double>(n + 2)) {
    for (int row = 0; row < m; row++) {
      for (int col = 0; col < n; col++) {
        val[row][col] = a[row][col];
      }
      bas[row] = n + row;
      val[row][n] = -1;
      val[row][n + 1] = b[row];
    }
    for (int col = 0; col < n; col++) {
      nb[col] = col;
      val[m][col] = -c[col];
    }
    nb[n] = -1;
    val[m + 1][n] = 1;
  }

  void pivot(int lr, int ec) {
    long double inv = 1 / val[lr][ec];
    for (int row = 0; row < m + 2; row++) {
      if (row == lr) {
        continue;
      }
      for (int col = 0; col < n + 2; col++) {
        if (col != ec) {
          val[row][col] -= val[lr][col] * val[row][ec] * inv;
        }
      }
    }
    for (int col = 0; col < n + 2; col++) {
      if (col != ec) {
        val[lr][col] *= inv;
      }
    }
    for (int row = 0; row < m + 2; row++) {
      if (row != lr) {
        val[row][ec] *= -inv;
      }
    }
    val[lr][ec] = inv;
    std::swap(bas[lr], nb[ec]);
  }

  bool optimize(int ph) {
    int oi = ph == 1 ? m + 1 : m;
    while (true) {
      int ent = -1;
      for (int col = 0; col <= n; col++) {
        if (ph == 2 && nb[col] == -1) {
          continue;
        }
        if (ent == -1 || val[oi][col] < val[oi][ent] - eps ||
            (std::abs(val[oi][col] - val[oi][ent]) <= eps &&
             nb[col] < nb[ent])) {
          ent = col;
        }
      }
      if (ent == -1) {
        return true;
      }
      if (val[oi][ent] >= -eps) {
        return true;
      }
      int lea = -1;
      for (int row = 0; row < m; row++) {
        if (val[row][ent] <= eps) {
          continue;
        }
        if (lea == -1) {
          lea = row;
          continue;
        }
        long double lhs = val[row][n + 1] / val[row][ent];
        long double rhs = val[lea][n + 1] / val[lea][ent];
        if (lhs < rhs - eps ||
            (std::abs(lhs - rhs) <= eps && bas[row] < bas[lea])) {
          lea = row;
        }
      }
      if (lea == -1) {
        return false;
      }
      pivot(lea, ent);
    }
  }

  linear_program_result solve() {
    int mi = 0;
    for (int row = 1; row < m; row++) {
      if (val[row][n + 1] < val[mi][n + 1]) {
        mi = row;
      }
    }
    if (m > 0 && val[mi][n + 1] < -eps) {
      pivot(mi, n);
      if (!optimize(1) || val[m + 1][n + 1] < -eps) {
        return {linear_program_status::infeasible, 0, {}};
      }
      if (std::abs(val[m + 1][n + 1]) > eps) {
        return {linear_program_status::infeasible, 0, {}};
      }
      for (int row = 0; row < m; row++) {
        if (bas[row] != -1) {
          continue;
        }
        int ent = 0;
        for (int col = 1; col <= n; col++) {
          if (std::abs(val[row][col]) > std::abs(val[row][ent]) + eps ||
              (std::abs(std::abs(val[row][col]) - std::abs(val[row][ent])) <=
                   eps &&
               nb[col] < nb[ent])) {
            ent = col;
          }
        }
        if (std::abs(val[row][ent]) > eps) {
          pivot(row, ent);
        }
      }
    }
    if (!optimize(2)) {
      return {linear_program_status::unbounded,
              std::numeric_limits<long double>::infinity(),
              {}};
    }
    std::vector<long double> sol(n);
    for (int row = 0; row < m; row++) {
      if (bas[row] < n) {
        sol[bas[row]] = val[row][n + 1];
      }
    }
    return {linear_program_status::optimal, val[m][n + 1], std::move(sol)};
  }
};

} // namespace simplex_internal

/// @brief Maximize c*x subject to A*x <= b and x >= 0 with a two-phase
/// simplex tableau; reports optimal, infeasible, or unbounded.
inline linear_program_result
simplex(const std::vector<std::vector<long double>> &a,
        const std::vector<long double> &b, const std::vector<long double> &c) {
  assert(a.size() == b.size());
  for (const auto &row : a) {
    assert(row.size() == c.size());
  }
  return simplex_internal::tableau(a, b, c).solve();
}

} // namespace noya

#endif // NOYA_SIMPLEX_HPP
#include <algorithm>
#include <cassert>
#include <cmath>
#include <limits>
#include <utility>
#include <vector>

/// @complexity Time: Exponential worst case; typically polynomial pivot work per iteration.
/// Space: O(rows * columns).

namespace noya {

enum class linear_program_status { optimal, infeasible, unbounded };

struct linear_program_result {
  linear_program_status sta = linear_program_status::infeasible;
  long double obj = 0;
  std::vector<long double> sol;
};

namespace simplex_internal {

struct tableau {
  static constexpr long double eps = 1e-12L;
  // m constraints, n variables.
  int m;
  int n;
  std::vector<int> bas;
  std::vector<int> nb;
  std::vector<std::vector<long double>> val;

  tableau(const std::vector<std::vector<long double>> &a,
          const std::vector<long double> &b, const std::vector<long double> &c)
      : m(int(b.size())), n(int(c.size())), bas(m), nb(n + 1),
        val(m + 2, std::vector<long double>(n + 2)) {
    for (int row = 0; row < m; row++) {
      for (int col = 0; col < n; col++) {
        val[row][col] = a[row][col];
      }
      bas[row] = n + row;
      val[row][n] = -1;
      val[row][n + 1] = b[row];
    }
    for (int col = 0; col < n; col++) {
      nb[col] = col;
      val[m][col] = -c[col];
    }
    nb[n] = -1;
    val[m + 1][n] = 1;
  }

  void pivot(int lr, int ec) {
    long double inv = 1 / val[lr][ec];
    for (int row = 0; row < m + 2; row++) {
      if (row == lr) {
        continue;
      }
      for (int col = 0; col < n + 2; col++) {
        if (col != ec) {
          val[row][col] -= val[lr][col] * val[row][ec] * inv;
        }
      }
    }
    for (int col = 0; col < n + 2; col++) {
      if (col != ec) {
        val[lr][col] *= inv;
      }
    }
    for (int row = 0; row < m + 2; row++) {
      if (row != lr) {
        val[row][ec] *= -inv;
      }
    }
    val[lr][ec] = inv;
    std::swap(bas[lr], nb[ec]);
  }

  bool optimize(int ph) {
    int oi = ph == 1 ? m + 1 : m;
    while (true) {
      int ent = -1;
      for (int col = 0; col <= n; col++) {
        if (ph == 2 && nb[col] == -1) {
          continue;
        }
        if (ent == -1 || val[oi][col] < val[oi][ent] - eps ||
            (std::abs(val[oi][col] - val[oi][ent]) <= eps &&
             nb[col] < nb[ent])) {
          ent = col;
        }
      }
      if (ent == -1) {
        return true;
      }
      if (val[oi][ent] >= -eps) {
        return true;
      }
      int lea = -1;
      for (int row = 0; row < m; row++) {
        if (val[row][ent] <= eps) {
          continue;
        }
        if (lea == -1) {
          lea = row;
          continue;
        }
        long double lhs = val[row][n + 1] / val[row][ent];
        long double rhs = val[lea][n + 1] / val[lea][ent];
        if (lhs < rhs - eps ||
            (std::abs(lhs - rhs) <= eps && bas[row] < bas[lea])) {
          lea = row;
        }
      }
      if (lea == -1) {
        return false;
      }
      pivot(lea, ent);
    }
  }

  linear_program_result solve() {
    int mi = 0;
    for (int row = 1; row < m; row++) {
      if (val[row][n + 1] < val[mi][n + 1]) {
        mi = row;
      }
    }
    if (m > 0 && val[mi][n + 1] < -eps) {
      pivot(mi, n);
      if (!optimize(1) || val[m + 1][n + 1] < -eps) {
        return {linear_program_status::infeasible, 0, {}};
      }
      if (std::abs(val[m + 1][n + 1]) > eps) {
        return {linear_program_status::infeasible, 0, {}};
      }
      for (int row = 0; row < m; row++) {
        if (bas[row] != -1) {
          continue;
        }
        int ent = 0;
        for (int col = 1; col <= n; col++) {
          if (std::abs(val[row][col]) > std::abs(val[row][ent]) + eps ||
              (std::abs(std::abs(val[row][col]) - std::abs(val[row][ent])) <=
                   eps &&
               nb[col] < nb[ent])) {
            ent = col;
          }
        }
        if (std::abs(val[row][ent]) > eps) {
          pivot(row, ent);
        }
      }
    }
    if (!optimize(2)) {
      return {linear_program_status::unbounded,
              std::numeric_limits<long double>::infinity(),
              {}};
    }
    std::vector<long double> sol(n);
    for (int row = 0; row < m; row++) {
      if (bas[row] < n) {
        sol[bas[row]] = val[row][n + 1];
      }
    }
    return {linear_program_status::optimal, val[m][n + 1], std::move(sol)};
  }
};

} // namespace simplex_internal

/// @brief Maximize c*x subject to A*x <= b and x >= 0 with a two-phase
/// simplex tableau; reports optimal, infeasible, or unbounded.
inline linear_program_result
simplex(const std::vector<std::vector<long double>> &a,
        const std::vector<long double> &b, const std::vector<long double> &c) {
  assert(a.size() == b.size());
  for (const auto &row : a) {
    assert(row.size() == c.size());
  }
  return simplex_internal::tableau(a, b, c).solve();
}

} // namespace noya