Fourier–Motzkin elimination
Fourier–Motzkin elimination is the analogue for inequalities of Gaussian elimination for equations[1][2]. To remove a variable from a system of linear inequalities, write each inequality that mentions as a lower bound or an upper bound , where and are linear in the other variables. A value of exists if and only if every lower bound is at most every upper bound, so the inequalities in can be replaced by all the inequalities . The solution set of the new system is the projection of the old one along [4].
The elimination step
Scale each inequality so that has coefficient or :
and replace them by the inequalities , keeping the inequalities that do not mention . Each new inequality is a non-negative combination of two old ones, so every solution of the old system satisfies the new one; conversely, given a solution of the new system, any between and extends it. Once every variable is eliminated, the system is a list of comparisons between constants, and it is satisfiable if and only if they all hold[4].
Fourier–Motzkin elimination over the rationals in OCaml, using Zarith.
(* A constraint a1 x1 + ... + an xn <= b over the rationals, with thecoefficients in an array indexed by variable. *)type c = { a : Q.t array; b : Q.t }(* Eliminate variable k: every lower bound on xk is combined with everyupper bound, and constraints without xk are kept. *)let eliminate k cs =let pos = List.filter (fun c -> Q.sign c.a.(k) > 0) csand neg = List.filter (fun c -> Q.sign c.a.(k) < 0) csand zero = List.filter (fun c -> Q.sign c.a.(k) = 0) cs inlet comb p n =(* scale so that xk has coefficient +1 in p and -1 in n, then add *)let sp = Q.inv p.a.(k) and sn = Q.inv (Q.neg n.a.(k)) in{ a = Array.mapi (fun i x -> Q.add (Q.mul sp x) (Q.mul sn n.a.(i))) p.a; b = Q.add (Q.mul sp p.b) (Q.mul sn n.b) }in(* drop constraints 0 <= b that hold trivially *)List.filter (fun c -> Array.exists (fun x -> Q.sign x <> 0) c.a || Q.sign c.b < 0)(zero @ List.concat_map (fun p -> List.map (comb p) neg) pos)(* Bounds on xk once x0 .. x(k-1) are fixed, from the constraints thatmention no later variable. *)let bounds k xs cs =List.fold_left (fun (lo, hi) c ->let rest = ref c.b inArray.iteri (fun i x -> if i < k then rest := Q.sub !rest (Q.mul c.a.(i) x)) xs;let s = Q.sign c.a.(k) inif s > 0 then (lo, Q.min hi (Q.div !rest c.a.(k)))else if s < 0 then (Q.max lo (Q.div !rest c.a.(k)), hi)else (lo, hi))(Q.minus_inf, Q.inf) cslet pp names c =let term first (x, v) =let mag = Q.abs x inlet coef = if Q.equal mag Q.one then "" else Q.to_string mag ^ " " in(if Q.sign x < 0 then (if first then "-" else " - ") else if first then "" else " + ") ^ coef ^ vinlet terms = List.filter (fun (x, _) -> Q.sign x <> 0) (Array.to_list (Array.mapi (fun i x -> (x, names.(i))) c.a)) in(if terms = [] then "0" else String.concat "" (List.mapi (fun i t -> term (i = 0) t) terms))^ " <= " ^ Q.to_string c.b
Eliminating z and y from a system in three variables, then back-substituting.
open Fmlet names = [| "x"; "y"; "z" |]let c l b = { a = Array.of_list (List.map Q.of_int l); b = Q.of_int b }(* Is there a point with x + y + z <= 8, x - y >= -1, y + 2z >= 7,x >= 1, z <= 3, and x + z >= 5 ? *)let system = [ c [ 1; 1; 1 ] 8; c [ -1; 1; 0 ] 1; c [ 0; -1; -2 ] (-7); c [ -1; 0; 0 ] (-1); c [ 0; 0; 1 ] 3; c [ -1; 0; -1 ] (-5) ]let show title cs = Printf.printf "%s (%d)\n" title (List.length cs); List.iter (fun c -> print_endline (" " ^ pp names c)) cs
Running it.
system (6)x + y + z <= 8-x + y <= 1-y - 2 z <= -7-x <= -1z <= 3-x - z <= -5z eliminated (6)-x + y <= 1-x <= -1x + 1/2 y <= 9/2y <= 3-1/2 y <= -1/2-x <= -2y eliminated (4)-x <= -1-x <= -2-x <= 02 x <= 8x in [2, 4], take 3y in [1, 3], take 2z in [5/2, 3], take 11/4all six constraints hold: true
Back-substitution goes in the opposite order: is chosen within the bounds of the final system, then within the bounds the previous system gives once is fixed, and so on. Each interval is non-empty because of the elimination step.
Growth
One step can replace inequalities by , up to from . After steps the bound is doubly exponential in , and most of the new inequalities are redundant[4][6].
Twelve random inequalities in four variables, eliminating three of them.
open Fm(* m random constraints in n variables; eliminate them one by one. *)let () =Random.init 4;let n = 4 and m = 12 inlet cs = ref (List.init m (fun _ ->{ a = Array.init n (fun _ -> Q.of_int (Random.int 11 - 5)); b = Q.of_int (Random.int 20) })) inPrintf.printf "start: %d constraints\n" (List.length !cs);for k = n - 1 downto 1 docs := eliminate k !cs;Printf.printf "after eliminating x%d: %d constraints\n" k (List.length !cs)done
Running it.
start: 12 constraintsafter eliminating x3: 31 constraintsafter eliminating x2: 234 constraintsafter eliminating x1: 13412 constraints
Removing redundant inequalities after each step, with a linear programming check for each, keeps the growth singly exponential, since a projection of a polyhedron has at most exponentially many facets. In practice, satisfiability of large systems is decided with the simplex method instead; Fourier–Motzkin remains useful for projection, for small systems, and for producing explanations, since each derived inequality records which originals it combines[6].
Integers
Over the integers the step is unsound for satisfiability: the projection of the integer points is not the set of integer points of the projection.
A system with rational but no integer solutions.
open Fm(* 1 <= 2x <= 1 and y = x: satisfiable over the rationals, not the integers. *)let () =let names = [| "x"; "y" |] inlet c l b = { a = Array.of_list (List.map Q.of_int l); b = Q.of_int b } inlet cs = [ c [ 2; 0 ] 1; c [ -2; 0 ] (-1); c [ 1; -1 ] 0; c [ -1; 1 ] 0 ] inlet s = eliminate 1 cs inList.iter (fun c -> print_endline (pp names c)) s;let lo, hi = bounds 0 [| Q.zero; Q.zero |] s inPrintf.printf "x in [%s, %s]\n" (Q.to_string lo) (Q.to_string hi)
Running it.
2 x <= 1-2 x <= -1x in [1/2, 1/2]
Elimination leaves , which is not an integer. Pugh's Omega test extends Fourier–Motzkin to integers by computing an exact shadow when coefficients allow, and otherwise a dark shadow and a finite set of splinters[5]. SMT solvers for linear integer arithmetic use related projections alongside branch and bound[6].
History
Joseph Fourier described the method in 1826 as part of his work on systems of inequalities[1]. Lloyd Dines rediscovered it in 1919[3], and Theodore Motzkin rediscovered it in his 1936 dissertation, from which the double name comes[2]. William Pugh built the Omega test on it in 1991 for dependence analysis in compilers[5].
see also
- Simplex methodThe simplex method solves linear programs, maximizing a linear function subject to linear inequalities, by walking along the edges of the feasible polyhedron from vertex to vertex, each step improving the objective, until no neighbouring vertex is better. Each step is a pivot on a tableau. It is fast in practice and in smoothed analysis, but takes exponential time on the Klee-Minty cubes with Dantzig's pivoting rule, and needs a rule such as Bland's to avoid cycling on degenerate problems. Its final tableau also gives a solution of the dual program, certifying optimality.
- Difference logicDifference logic is the fragment of linear arithmetic whose atoms bound the difference of two variables by a constant, x − y ≤ c, over the integers or the reals. A conjunction of such constraints is satisfiable exactly when the graph with an edge from y to x of weight c for each constraint has no negative cycle, so it can be decided by shortest-path algorithms such as Bellman–Ford, and a negative cycle is a short explanation of unsatisfiability. Boolean combinations of difference constraints are handled by SMT solvers through DPLL(T); they express scheduling problems and the timing constraints of timed automata.
- Theory solverIn an SMT solver, a theory solver decides whether a conjunction of literals of one theory, such as linear arithmetic or equality with uninterpreted functions, is consistent. The SAT search asserts literals to it incrementally and asks it to check them; on inconsistency it returns an explanation, a small subset of the asserted literals that is already inconsistent, which the SAT search learns as a clause. A theory solver must also undo assertions when the search backtracks, and it may propagate literals that the asserted ones imply. The quality of explanations and propagation largely determines the solver's performance.
- Refinement typeA refinement type is a base type paired with a logical predicate that its values must satisfy, such as {v : Int | v > 0}, the positive integers. Type checking generates verification conditions, implications between predicates, and discharges them with an SMT solver, so a program can be checked against preconditions, postconditions and invariants without the programmer writing proof terms. The predicates are restricted to a decidable logic, typically linear arithmetic with uninterpreted functions, which is what keeps checking automatic. LiquidHaskell, F* and Liquid Types are the best-known systems.
further reading
- [1]J. B. J. Fourier, “Solution d’une question particulière du calcul des inégalités”, Nouveau Bulletin des Sciences par la Société Philomathique de Paris (1826).
- [2]T. S. Motzkin, Beiträge zur Theorie der linearen Ungleichungen, dissertation, Universität Basel (1936).
- [3]L. L. Dines, “Systems of linear inequalities”, Annals of Mathematics 20 (1919).
- [4]A. Schrijver, Theory of Linear and Integer Programming, §12.2, Wiley (1986).
- [5]W. Pugh, “The Omega test: a fast and practical integer programming algorithm for dependence analysis”, Communications of the ACM 35 (1992).
- [6]D. Kroening, O. Strichman, Decision Procedures: An Algorithmic Point of View, ch. 5, Springer (2008; 2nd ed. 2016).
last updated