Linear programming (LP) is not itself an approximation problem, as it can be solved exactly in polynomial time. The method however is used in many approximation algorithms (especially in all Iterative Rounding algorithms).
Linear programs can be expressed in canonical form:
\begin{eqnarray*} \mbox{maximize:} & \mathbf{c}^{T} \mathbf{x} \\ \mbox{subject to:} & A\mathbf{x} \leq \mathbf{b} \\ \mbox{and:} & \mathbf{x} \geq \mathbf{0} \\ \end{eqnarray*}
where \(\mathbf{x}\) represents the vector of variables to be determined, \(\mathbf{b}\) and \(\mathbf{c}\) are vectors of given coefficients and \(A\) is a given matrix of coefficients.
We provide an interface for creating, modifying and (re)solving LP instances using the primal and dual simplex methods. As the underlying implementation we use the GLPK (GNU Linear Programming Kit) library. Although the simplex method is not guaranteed to work in polynomial time, it usually works equally fast or faster then polynomial algorithms.
complete usage example can be found in file lp_example.cpp
Some of the LP formulations used in different algorithms have an exponential number of rows. To solve such instances we use a technique called row generation, which uses the concept of a "separation oracle". The separation oracle is an algorithm, which given the values of LP variables checks if they form a feasible solution to the LP instance and if not, it returns some violated LP row.
Using a separation oracle we can create a simple method of solving LPs with exponential number of rows:
solve_lp_row_generation() { init(LP) solve(LP) while (not SeparationOracle.feasible_solution(LP)) { LP <- SeparationOracle.add_violated_row(LP) resolve(LP) } }
In other words we start with a subset (maybe empty) of rows and solve the LP, and then while the obtained solution is infeasible add a violated row to the LP and resolve it.
The row generation LP (re)solving is implemented by two classes: paal::lp::row_generation_solve_lp and paal::lp::row_generation_resolve_lp, both of which implement the following interface:
row_generation_solve_lp { paal::lp::problem_type operator(Problem & problem, LP & lp); }
Here LP is the linear programming instance type.
Problem is the problem instance type: a class representing the problem described by the LP.
Problem must implement the following interface:
ProblemArchetype { Oracle & get_oracle(); }
Oracle is the Separation Oracle concept class. It is responsible for checking if the current LP solution is a feasible solution to the given Problem and if not, adding some violated constraint to the LP. It must implement the following interface:
OracleArchetype { bool feasible_solution(const Problem & problem, const LP & lp);
void add_violated_constraint(const Problem & problem, LP & lp); }
Typical separation oracles iterate over a set of the constraints that could be violated and choose one violated row to be added to the LP. The library provides a number of typical separation oracle strategies using this model:
To use one of those oracles the user must provide a ViolationsChecker concept class specific to the given problem. This class is responsible for:
ViolationsChecker must implement the following interface:
ViolationsCheckerArchetype { CandidateIteratorRange get_violation_vandidates(const Problem & problem, const LP & lp);
boost::optional<double> check_violation(Candidate candidate, const Problem & problem);
void add_violated_constraint(Candidate candidate, const Problem & problem, LP & lp); }
CandidateIteratorRange is an iterator range of possible constraints (candidates) that could be violated. Candidate is a single constraint (candidate) that could be violated.
http://www.gnu.org/software/glpk/ - GLPK library (used in PAAl for LP solving)