/* * lpsolve.cc * * Copyright 2017 Luka Marohnić * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * * Description * ^^^^^^^^^^^ * Function 'lpsolve' solves a (mixed integer/binary) LP problem in general * form. It is used as follows: * * lpsolve (obj, constr, bd, opts) * * Parameters: * * obj - (mandatory) linear objective function (it is allowed to have a constant term) * constr - (optional) list of linear constraints * bd - (optional) bounds for one or more variables * opts - (optional) equation(s) of the form 'option=value', where * 'option' is one of: 'assume', 'lp_variables', 'lp_integervariables, * 'lp_binaryvariables', 'lp_maximize' or 'lp_depthlimit' * * The problem may be given in symbolic form or in matrix form. The objective * function may be given as symbolic expression (including an optional constant * term) and the constraints as equations and inequalities. Also, a constraint * may be given in form 'Expr=a..b', where Expr is a linear expression and a,b * are real constants (possibly +/-infinity). * * It is also possible to enter constraints of type 'Expr ∈ [a,b]' as 'Expr=a..b', * where Expr is a linear expression and a, b are real numbers. * * For problems given in matrix form, the parameter 'obj' is a vector c and 'constr' is * a list [A, b, Aeq, beq] where A,Aeq are matrices and b,beq are vectors such that * c.x is maximized/minimized subject to A.x<=b and Aeq.x=beq. Parameters Aeq and beq * may be omitted if the problem does not have equality constraints. * * For symbolically given problems, the parameter 'bd' is a sequence of * arguments in the form 'name=range', where 'range' is an interval (for * example, x=-5..5 or y=0..inf). For problems given in matrix form, 'bd' must * be a vector of two vectors bl and bu such that bl<=x<=bu. In that case, all * decision variables are bounded at once. For variables that are unbounded * from above/below one can use +/-infinity as an upper/lower "bound". * * All decision variables are unbounded and continuous by default. * * With the 'assume' option one can specify: * * - assume = integer or lp_integer (all variables are integers) * - assume = lp_binary (all variables are binary) * - assume = lp_nonnegative (all variables are nonnegative) * - assume = nonnegint or lp_nonnegint (all variables are nonnegative integers) * * The following options are used to specify continuous, integer and binary * variables in mixed LP problems: * * - lp_variables = * - lp_integervariables = * - lp_binaryvariables = * * Instead of a list of identifiers, a list or an interval of their indexes may * be given as the value for 'lp_(integer/binary)variables'. For example, * option 'lp_binaryvariables=m..n' specifies xm,x(m+1),...,xn as binary * variables, and option 'lp_integervariables=[1,3,7]' specifies that the * variables x1, x3 and x7 are integers. The indexes and identifiers may be * freely mixed within a list. Variable indexes are 1-based. * * Objective function can be either maximized or minimized. By default it is * minimized, which is equivalent to the (superfluous) option * * - lp_maximize = false * * To solve a maximization problem, one can specify: * * - lp_maximize [= true] * * Integer (mixed) LP problems are solved using the branch and bound method, * during which a binary tree of nodes that need to be inspected is formed. The * depth of that tree can be limited with option * * - lp_depthlimit = * * and number of inspected nodes can be limited with option * * - lp_nodelimit = * With 'lp_depthlimit' or 'lp_nodelimit' option set, the integer solution * returned by the branch&bound method is feasible, but not necessarily the * optimal one. Also, it is possible that no feasible solution is found, * although one may exist. By setting 'lp_depthlimit' to zero (which is the * default), the feasible solution returned by the branch&bound method is * always optimal. * * By default, 'lpsolve' uses two-phase simplex method for optimization of * the objective function. * * The function 'lpsolve' returns either * * - an empty list if no feasible solution exists, * - the list [+infinity,[...]] if the objective function required to be maximized * is unbounded from above, i.e. the list [-infinity,[...]] if the objective * function required to be minimized is unbounded from below, or * - the optimal solution as a list [optimum,[x1=x1*,x2=x2*,...,xn=xn*]] for * symbolically given problems, where x1,x2,...,xn are the decision variables, * or as a list [optimum,[x1*,x2*,...,xn*]] for problems given in matrix form. * * If one needs only a fesible point with respect to a set of constraints, one * should set the parameter 'obj' to 0 for symbolic problems i.e. to [] for * matrix problems. The return value is the feasible point as a vector of * coordinates or an empty list if the given set of constraints is * contradictory. * * The function returns an error if no constraints were detected (i.e. when * none of 'constr', 'bd' and 'assume=(lp_)nonneg(ative/int)' arguments were * detected). * * Examples * ^^^^^^^^ * 1) Use LPSolve to minimize a linear function subject to linear constraints. * Remember that all decision variables are unrestricted in sign by default. * * lpsolve(2x+y-z+4,[x<=1,y>=2,x+3y-z=2,2x-y+z<=8,-x+y<=5]) * lpsolve(-4x-5y,[x+2y<=6,5x+4y<=20,0<=x,0<=y]) * * 2) Use the 'lp_maximize [= true]' option to maximize the objective function. * * lpsolve(-7x+2y,[4x-12y<=20,-x+3y<=3],x=-5..5,y=0..inf,lp_maximize=true) * lpsolve(x-y-2z+3,[-3x-y+z<=3,2x-3y>=4z,x-z=y,x>=0,y<=0],lp_maximize) * * 3) Use the 'assume = lp_nonnegative' option instead of including * non-negative constraints explicitly. * * lpsolve(-x-y,[y<=3x+1/2,y<=-5x+2],assume=lp_nonnegative) * lpsolve(x+y,[x<=8,-x+y<=4,-x+2y>=6,2x+y<=25,3x+y>=18,-x+2y>=6],assume=lp_nonnegative) * lpsolve(45.55x1+21.87x2,[1.64x1+2.67x2<=31.2,2.11x1+2.3x2>=13.9],assume=lp_nonnegative) * lpsolve(3x+4y,[x<=4,x+3y<=15,-x+2y>=5,x-y>=9,x+y=6],assume=lp_nonnegative,lp_maximize=true) * * 4) Simple bounds can be added separately. * * lpsolve(-6x+4y+z,[5x-10y<=20,2z-3y=6,-x+3y<=3],x=1..20,y=0..inf) * * 5) Use the 'integer' or 'lp_integervariables' option to solve integer * programming problems. * * lpsolve(-5x-7y,[7x+y<=35,-x+3y<=6],assume=integer) * lpsolve(x+3y+3z,[x+3y+2z<=7,2x+2y+z<=11],assume=lp_nonnegative,lp_integervariables=[x,z],lp_maximize) * * 6) The 'nonnegint' option can be used to get non-negative integer values. * * lpsolve(2x+5y,[3x-y=1,x-y<=5],assume=nonnegint) * lpsolve(x1+x2,[2x1+5x2<=16,6x1+5x2<=30],assume=nonnegint,lp_maximize) * * 7) Use the 'lp_binary' option to solve binary integer programming problems, * where the decision variables can only have values from the set {0,1}. * * lpsolve(8x1+11x2+6x3+4x4,[5x1+7x2+4x3+3x4<=14],assume=lp_binary,lp_maximize) * * 8) LP problems can also be given in matrix form. * * c:=[-2,1];A:=[[-1,1],[1,1],[-1,0],[0,-1]];b:=[3,5,0,0];lpsolve(c,[A,b]) * c:=[-1,1];A:=[[-3,1],[5,1]];b:=[1/2,2];lpsolve(c,[A,b],assume=lp_nonnegative) * * 9) Bounds for problem in matrix form can be added separately. In * the following example, there are no other constraints. * * c:=[-2,5,-3];bl:=[2,3,1];bu:=[6,10,3.5];lpsolve(c,[],[bl,bu]) * * 10) For problems in matrix form with no inequality constraints, an empty list * is passed for parameters A and b. * * c:=[4,5];Aeq:=[[-1,1.5],[-3,2]];beq:=[2,3];lpsolve(c,[[],[],Aeq,beq]) * * 11) The 'lp_integervariables' or 'lp_binaryvariables' option can be used to * specify mixed-integer problems. * * c:=[2,-3,-5];A:=[[-5,4,-5],[2,5,7],[2,-3,4]];b:=[3,1,-2];lpsolve(c,[A,b],lp_integervariables=[1,3]) * * Additional LP problems for testing * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * 1) Maximize * * z = -3x1 + 2x2 - x3 + 4x4 * * subject to: * * x1 + x2 - 4x3 + 2x4 ≥ 4, * -3x1 + x2 - 2x3 ≤ 6, * x2 - x4 = -1, * x1 + x2 - x3 = 0, * x3, x4 ≥ 0. * * * 2) Minimize or maximize * * z = 28x11 + 84x12 + 112x13 + 112x14 + 60x21 + 20x22 * + 50x23 + 50x24 + 96x31 + 60x32 + 24x33 + 60x34 * + 64x41 + 40x42 + 40x43 + 16x44 + 50y1 + 50y2 * + 50y3 + 50y4 * * subject to: * * x11 + x12 + x13 + x14 = 1, * x21 + x22 + x23 + x24 = 1, * x31 + x32 + x33 + x34 = 1, * x41 + x42 + x43 + x44 = 1, * -x11 - x21 - x31 - x41 + 4y1 ≥ 0, * -x12 - x22 - x32 - x42 + 4y2 ≥ 0, * -x13 - x23 - x33 - x43 + 4y3 ≥ 0, * -x14 - x24 - x34 - x44 + 4y4 ≥ 0, * xij, zi ∈ {0,1} for all i,j=1,2,3,4. * * * 3) (from cascmd_fr) Minimize * * z = 2x + y - z + 4 * * subject to: * * x ≤ 1, * y ≥ 2, * x + 3y - z = 2, * 2x - y + z ≤ 8, * -x + y ≤ 5. * * * 4) The problem of putting as much queens as possible on an 8x8 chessboard so that they don't threat each other. * * Maximize * c.x, * where c = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] * subject to: * A.x<=b * where A = [[1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0],[0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0],[0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0],[0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0],[0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],[0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],[0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0],[0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0]] * and b = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] * (42 constraints in total) * 64 decision variables x=(x11,x12,...,x18,x21,x22,...,x28,...,x81,x82,...,x88), all binary * xij=1 if there is a queen on field in ith row and jth column of a chessboard, xij=0 if the field is empty * * 5) * [2,10,13,17,7,5,7,3],[[],[],[[22,13,26,33,21,3,14,26],[39,16,22,28,26,30,23,24],[18,14,29,27,30,38,26,26],[41,26,28,36,18,38,16,26]],[7872,10466,11322,12058]],assume=lp_nonnegative * * 6) * 1.06x1+0.56x2+3x3+2703.50x4+4368.23x5,[1.06x1+0.015x3>=729824.87,0.56x2+0.649x3>=1522188.03,x3>=1680.05,x4>=60,x5>=4],assume=lp_nonnegative */ using namespace std; #include "lpsolve.h" #include "giac.h" #ifndef NO_NAMESPACE_GIAC namespace giac { #endif // ndef NO_NAMESPACE_GIAC /* * Maximize obj^t*x subject to A*x<=b using two-phase simplex method. Slack and * surplus variables need to be represented with additional columns in matrix * m_orig. Equality constraints must come before inequality constraints in that * matrix. Function introduces artificial variables automatically when * preparing for the first phase. This implementation is intended for exact * computation, phase I may fail if calculations are floating-point because of * roundoff errors (see example 6 in 'Additional LP problems for testing' * section above). */ matrice simplex_twophase(matrice & m_orig,vecteur & b,vecteur & obj,vecteur & soln,gen & optimum,GIAC_CONTEXT) { int nc=m_orig.size(),nv=obj.size(); int ns=int(m_orig.front()._VECTptr->size())-nv; int neq=nc-ns; matrice m(m_orig),idn(midn(nc)); // append columns for artificial variables and b (rhs values) vector av; m=mtran(m); for (int i=-neq;i::const_iterator it=av.begin();it!=av.end();++it) { br=subvecteur(br,*m[*it]._VECTptr); } m.push_back(br); m=simplex_reduce(m,soln,optimum,true,false,contextptr); // phase I if (!is_zero(optimum,contextptr)) { // no feasible solution exists soln=vecteur(0); return m; } vector< pair > basis; vector found(nc+1,false); // found[i] becomes true when ith column of idn is found vector row_active(nc+1,true); // a constraint is inactive when tautologic (0=0) for (int j=0;j=0) { I=-1; break; } I=i; } } if (I==-1 || found[I] || !is_one(m[I][j])) // jth variable not in the basis, skip continue; // jth variable is in the basis found[I]=true; // remember which idn columns have been found so far if (j>=nv+ns) { // jth variable is artificial, it must be pushed out of the basis int k=0; for (;k >::const_iterator it=basis.begin();it!=basis.end();++it) { br=subvecteur(br,multvecteur(br[it->second],*m[it->first]._VECTptr)); } m.push_back(br); for (int i=nc;i>=0;--i) { if (!row_active[i]) m.erase(m.begin()+i); } m=simplex_reduce(m,soln,optimum,true,false,contextptr); // phase II } return m; } /* * Assure that b contains no negative elements. */ void simplex_prepare(matrice & m,vecteur & b,GIAC_CONTEXT) { for (int i=0;iat(j)=A[l]._VECTptr->at(j)-s; // cout << "(" << l << "," << j << "): " << L[l][j] << endl; } } for (k=0;kat(k)=L[j][k]*c; } } return true; } static int step_count; /* * Implementation of primal-dual affine scaling algorithm from: * http://www.4er.org/CourseNotes/Book%20B/B-III.pdf */ bool primal_dual_affine_scaling(matrice & A,vecteur & b,vecteur & c, vecteur & x,vecteur & p,vecteur & s,GIAC_CONTEXT) { gen alpha(0.99995),beta(0.1),mu; int nv=A.front()._VECTptr->size(),nc=A.size(); matrice At(mtran(A)),L; vecteur xinv(nv),dx(nv),ds; do { ++step_count; mu=beta*scalarproduct(s,x,contextptr)/gen(nv); for (int k=0;kbegin(),At[j]._VECTptr->begin()+i+1); B[i]=addvecteur(*B[i]._VECTptr,multvecteur(cf,r)); rh2[i]+=cf*rh1[j]; } } if (!cholesky_decomposition(B,L,contextptr)) // can't iterate further break; vecteur dp(rh2); for (int i=0;i=0;--j) { for (int i=j+1;isize()-nv,nc=A.size(); vecteur c(mergevecteur(-c_orig,vecteur(ns,gen(0)))); if (x.empty()) x=vecteur(nv,gen(1)); vecteur y(ns); for (int k=0;k vartype,gen & itol,GIAC_CONTEXT) { for (int i=0;i & vartype,vecteur & eq,GIAC_CONTEXT) { int i=0,nv=vartype.size(); for (;is_zero(m[i][J]);++i); eq=*m[i]._VECTptr; eq[J]=gen(0); eq.push_back(b[i]); eq=multvecteur(-m[i][J],eq); gen itol(0); if (!is_whole(eq.back(),itol,contextptr)) return false; for (int j=0;j & vartype,GIAC_CONTEXT) { int n=m.front()._VECTptr->size(),nv=vartype.size(); vecteur eq(*M[I]._VECTptr),cut(n,gen(0)); gen f0(eq.back()-_floor(eq.back(),contextptr)); if (is_greater(f0,gen(0.99),contextptr) || is_strictly_greater(gen(0.01),f0,contextptr)) // f0 or 1-f0 is too small return vecteur(0); eq.pop_back(); cut.push_back(gen(1)); gen cfmin(0),cfmax(0); for (int j=0;j=nv) { cut=addvecteur(cut,multvecteur(cut[j],eqj)); cut[j]=gen(0); } } if (is_greater(_abs(_evalf(cfmax/cfmin,contextptr),contextptr),pow(gen(10),6),contextptr)) // dynamism of the cut is too large return vecteur(0); int nz=nv; for (int i=0;i1+nv/2) // the cut is too dense return vecteur(0); cut=*exact(cut,contextptr)._VECTptr; gen nzv(_abs(_denom(cut.back(),contextptr),contextptr)); for (int i=0;i & vartype, vecteur & soln,gen & optimum,GIAC_CONTEXT) { int nv=obj.size(),discarded_cuts=0; if (soln.empty()) return false; matrice cuts; gen itol(0); for (int j=0;j_VECTptr); m_copy.push_back(v); } return m_copy; } static int node_count; void branch_and_bound(matrice & m_orig,vecteur & b_orig,vecteur & obj,vector & varsign, vector & vartype,vecteur & soln_relaxed,vecteur & incumbent,gen & optimum, gen & itol,bool use_affscale,int depth,int depthlimit,int nodelimit,GIAC_CONTEXT) { if ((depthlimit>0 && depth>depthlimit) || (nodelimit>0 && node_count>=nodelimit)) return; int nv=int(obj.size()),nc=m_orig.size(); // number of variables int ns=int(m_orig.front()._VECTptr->size())-nv; int k=-1; gen fr,min_fr(0); for (int i=0;i nxt; gen xk=soln_relaxed[k]; vecteur bounds=makevecteur(_ceil(xk,contextptr),_floor(xk,contextptr)); vecteur solnv(2),optv(2),mv(2),bv(2); // solve two subproblems matrice idn(midn(nv+ns)); for (int i=0;i<2;++i) { matrice m(copy_matrix(m_orig)); vecteur b(b_orig),soln; for (int j=0;jpush_back(gen(0)); } vecteur c(*idn[k]._VECTptr); c.push_back(gen(2*i-1)); m.push_back(c); b.push_back(bounds[i]); if (i==0 && is_strictly_positive(bounds[i],contextptr)) { int l=0; for (vector::const_iterator it=varsign.begin();it!=varsign.end();++it) { if (*it==0 && (k==l || k==l+1)) { c=*idn[k+(k==l?1:-1)]._VECTptr; c.push_back(gen(0)); m=mergevecteur(vecteur(1,c),m); b=mergevecteur(vecteur(1,gen(0)),b); } l+=*it==0?2:1; } } if (use_affscale) { optv[i]=affscale(m,b,obj,soln,false,contextptr); soln=*_epsilon2zero(soln,contextptr)._VECTptr; } else { matrice M(simplex_twophase(m,b,obj,soln,optv[i],contextptr)); simplex_gomory(M,m,b,obj,vartype,soln,optv[i],contextptr); } if (!soln.empty() && !is_inf(_abs(optv[i],contextptr)) && (incumbent.empty() || is_strictly_greater(optv[i],optimum,contextptr))) { if (exactify_solution(soln,vartype,itol,contextptr)) { // update incumbent solution incumbent=soln; optimum=optv[i]; } else { // mark this branch for further searching nxt.push_back(i); mv[i]=m; bv[i]=b; solnv[i]=soln; } } ++node_count; if (nodelimit>0 && node_count>=nodelimit) return; } // start with the branch that has higher upper bound if (nxt.size()==2 && is_strictly_greater(optv[1],optv[2],contextptr)) reverse(nxt.begin(),nxt.end()); // test marked branches recursively for (vector::const_iterator it=nxt.begin();it!=nxt.end();++it) { branch_and_bound(*mv[*it]._VECTptr,*bv[*it]._VECTptr,obj,varsign,vartype,*solnv[*it]._VECTptr, incumbent,optimum,itol,use_affscale,depth+1,depthlimit,nodelimit,contextptr); } } bool is_realcons(const gen & g,GIAC_CONTEXT) { if (g.type==_VECT) { vecteur & v = *g._VECTptr; for (vecteur::const_iterator it=v.begin();it!=v.end();++it) { if (!is_realcons(*it,contextptr)) return false; } return true; } return (is_inf(_abs(g,contextptr)) || _evalf(g,contextptr).type==_DOUBLE_); } bool lincomb_coeff(const gen & e_orig,const vecteur & v,vecteur & c,gen & r,GIAC_CONTEXT) { gen e(e_orig),a; c=vecteur(0); for (vecteur::const_iterator it=v.begin();it!=v.end();++it) { a=gen(0); if (is_constant_wrt(e,*it,contextptr) || (is_linear_wrt(e,*it,a,e,contextptr) && is_realcons(a,contextptr))) c.push_back(a); else return false; } return is_realcons(r=e,contextptr); } bool interval2vecteur(const gen & g,vecteur & v,GIAC_CONTEXT) { if (g.type!=_SYMB || !g.is_symb_of_sommet(at_interval)) return false; // g is not an interval v=*g._SYMBptr->feuille._VECTptr; return is_realcons(v,contextptr); } vector findvars(const vecteur & v, const vecteur & vars) { vector indexes; for (vecteur::const_iterator it=v.begin();it!=v.end();++it) { if ((*it).type==_IDNT) { // the variable is specified as an identifier // find its index in 'vars' int i=0; for (;isize()<2) return gensizeerr(contextptr); vecteur & gv = *g._VECTptr,constr,vars; vars=*_lname(gv[0],contextptr)._VECTptr; // detect variable names from the objective function int ibd; // position of the parameter 'bd' (or 'opts', if 'bd' is not given) if (gv[1].type==_VECT) { // the parameter 'constr' is given constr=*gv[1]._VECTptr; vecteur constr_vars=*_lname(gv[1],contextptr)._VECTptr; // scan variables in constraints // detect variables not appearing in the objective function for (vecteur::const_iterator it=constr_vars.begin();it!=constr_vars.end();++it) { if (!contains(vars,*it)) vars.push_back(*it); } ibd=2; } else // the parameter 'constr' is not given ibd=1; vecteur lr,bconstr,cvars,ivars,bvars,pvars,nvars,initp; int dl=0,nl=0; // branch&bound tree depth/node limit gen itol(0); bool maximize=false; bool all_nonneg=false; bool all_integer=false; bool all_binary=false; bool use_affscale=false; // check if any boundaries or options are set for (vecteur::const_iterator it=gv.begin()+ibd;it!=gv.end();++it) { if (is_integer(*it)) { switch(it->val) { case _LP_MAXIMIZE: maximize=true; break; } } else if (it->is_symb_of_sommet(at_equal)) { // parse the argument in form "option=value" vecteur ops(*it->_SYMBptr->feuille._VECTptr); if (ops[0].type==_IDNT && interval2vecteur(ops[1],lr,contextptr)) { // the boundaries for variable ops[0] are set if (!is_zero(lr[0]) && !is_inf(-lr[0])) bconstr.push_back(symbolic(at_superieur_egal,makevecteur(ops[0],lr[0]))); if (!is_zero(lr[1]) && !is_inf(lr[1])) bconstr.push_back(symbolic(at_inferieur_egal,makevecteur(ops[0],lr[1]))); if (is_positive(lr[0],contextptr)) // variable is nonnegative pvars.push_back(ops[0]); else if (!is_strictly_positive(lr[1],contextptr)) nvars.push_back(ops[0]); } if (ops[0]==at_assume) { // parse assumptions if (is_integer(ops[1])) { switch(ops[1].val) { case _ZINT: case _LP_INTEGER: // all variables are integer all_integer=true; ivars=vars; break; case _LP_BINARY: // all variables are binary all_nonneg=true; all_binary=true; bvars=vars; break; case _NONNEGINT: case _LP_NONNEGINT: // all variables are nonnegative integers all_nonneg=true; all_integer=true; ivars=vars; break; case _LP_NONNEGATIVE: // all variables are nonnegative all_nonneg=true; break; } } } if (is_integer(ops[0])) { switch(ops[0].val) { case _LP_VARIABLES: // specify continuous variables if (interval2vecteur(ops[1],lr,contextptr) && is_integer_vecteur(lr)) { for (int i=int(lr[0].val);i<=int(lr[1].val);++i) { cvars.push_back(gen(i)); } } else if (ops[1].type==_VECT) cvars=*ops[1]._VECTptr; break; case _LP_INTEGERVARIABLES: // specify integer variables if (interval2vecteur(ops[1],lr,contextptr) && is_integer_vecteur(lr)) { for (int i=int(lr[0].val);i<=int(lr[1].val);++i) { ivars.push_back(gen(i)); } } else if (ops[1].type==_VECT) ivars=*ops[1]._VECTptr; break; case _LP_BINARYVARIABLES: // specify binary variables if (interval2vecteur(ops[1],lr,contextptr) && is_integer_vecteur(lr)) { for (int i=int(lr[0].val);i<=int(lr[1].val);++i) { bvars.push_back(gen(i)); } } else if (ops[1].type==_VECT) bvars=*ops[1]._VECTptr; break; case _LP_DEPTHLIMIT: // specify branch&bound tree depth limit if (is_integer(ops[1])) dl=ops[1].val; break; case _LP_NODE_LIMIT: // specify branch&bound tree node limit if (is_integer(ops[1])) nl=ops[1].val; break; case _LP_INTEGER_TOLERANCE: // specify integer tolerance for MIP if (_evalf(ops[1],contextptr).type==_DOUBLE_ && is_positive(ops[1],contextptr)) itol=ops[1]; break; case _LP_INITIAL_POINT: // specify initial point for affine scaling algorithm if (ops[1].type==_VECT) initp=*ops[1]._VECTptr; use_affscale=true; break; case _LP_MAXIMIZE: // maximize=true/false for maximization/minimization maximize=is_one(ops[1]); break; case _LP_METHOD: if (is_integer(ops[1])) { switch (ops[1].val) { case _LP_SIMPLEX: use_affscale=false; break; case _LP_INTERIOR_POINT: use_affscale=true; break; } } break; } } } } gen obj_ct(0); // the constant term of the objective function int nv=0; // number of variables vector sv,varsign; // ith is 1 for variable xi nonnegative, -1 for negative and 0 for unconstrained matrice m; vecteur b,obj_orig; if (gv[0].type==_VECT && vars.size()==0) { // the problem is given in matrix form // objective: c=[c1,c2,...,cn], maximize/minimize c.x obj_orig=*gv[0]._VECTptr; nv=obj_orig.size(); // number of variables int n=constr.size(); if ((nv==0 && n==0) || (n!=0 && n!=4 && n!=2)) // improper specification of constraints, stop return gensizeerr(contextptr); if (n>0) { // constraints: [A,b,Aeq,beq] such that A.x<=b and/or Aeq.x=beq m=*constr[0]._VECTptr; // matrice A sv=vector(m.size(),-1); b=*constr[1]._VECTptr; // vecteur b if (m.size()!=b.size()) return gendimerr(contextptr); if (n==4) { // there are equation constraints vecteur &Aeq=*constr[2]._VECTptr,&beq=*constr[3]._VECTptr; if (!ckmatrix(Aeq) || Aeq.size()!=beq.size()) return gendimerr(contextptr); if (!Aeq.empty()) { m=mergevecteur(Aeq,m); b=mergevecteur(beq,b); } } if (!ckmatrix(m)) return gendimerr(contextptr); if (nv==0) { nv=m.front()._VECTptr->size(); obj_orig=vecteur(nv,gen(0)); } if (nv!=int(m.front()._VECTptr->size())) return gendimerr(contextptr); } varsign=vector(nv,all_nonneg ? 1 : 0); // check if the third argument is a vector of bounds for variables // [bl,bu] such that bl<=x<=bu if (int(gv.size())>ibd && gv[ibd].type==_VECT && ckmatrix(*gv[ibd]._VECTptr) && int(gv[ibd]._VECTptr->front()._VECTptr->size())==nv && int(gv[ibd]._VECTptr->size())==2) { // set boundaries to the variables vecteur & bl = *(*gv[ibd]._VECTptr)[0]._VECTptr; vecteur & bu = *(*gv[ibd]._VECTptr)[1]._VECTptr; matrice idn(midn(nv)); for (int i=0;i0) { // the problem is given in symbolic form // objective: c1*x1+c2*x2+...+cn*xn // constraints: a list of linear equations and inequalities gen r; if (!lincomb_coeff(gv[0],vars,obj_orig,obj_ct,contextptr)) { cout << "Error: the objective function must be linear" << endl; return gentypeerr(contextptr); } nv=vars.size(); varsign=vector(nv,all_nonneg?1:0); constr=mergevecteur(constr,bconstr); // write the constraints in matrix form for (vecteur::const_iterator it=constr.begin();it!=constr.end();++it) { vecteur sides(*((*it)._SYMBptr->feuille._VECTptr)),c,bnd; if (it->is_symb_of_sommet(at_equal) && interval2vecteur(sides[1],bnd,contextptr)) { // a bounded expression was given as constraint if (!lincomb_coeff(sides[0],vars,c,r,contextptr)) { cout << "Error: all constraints must be linear" << endl; return gentypeerr(contextptr); } m=mergevecteur(m,vecteur(2,c)); sv.push_back(1); sv.push_back(-1); b=mergevecteur(b,subvecteur(bnd,vecteur(2,r))); continue; } // turn constraint to vector form gen d=sides[0]-sides[1]; if (!lincomb_coeff(d,vars,c,r,contextptr)) { cout << "Error: all constraints must be linear" << endl; return gentypeerr(contextptr); } if (it->is_symb_of_sommet(at_equal)) { m=mergevecteur(vecteur(1,c),m); b=mergevecteur(vecteur(1,-r),b); continue; } else if (it->is_symb_of_sommet(at_inferieur_egal)) { if (d.type==_IDNT && find(vars.begin(),vars.end(),d)!=vars.end()) nvars.push_back(d); else { sv.push_back(-1); m.push_back(c); b.push_back(-r); } } else if (it->is_symb_of_sommet(at_superieur_egal)) { if (d.type==_IDNT && find(vars.begin(),vars.end(),d)!=vars.end()) pvars.push_back(d); else { sv.push_back(1); m.push_back(c); b.push_back(-r); } } else { cout << "Error: unrecognized relation type, it should be either >=, <= or =" << endl; return gentypeerr(contextptr); } } // update varsign vector vp=findvars(pvars,vars),vn=findvars(nvars,vars); for (vector::const_iterator vit=vp.begin();vit!=vp.end();++vit) { varsign[*vit]=1; } for (vector::const_iterator vit=vn.begin();vit!=vn.end();++vit) { varsign[*vit]=-1; } } else // the problem is given improperly, stop return gentypeerr(contextptr); if (m.empty()) { cout << "Error: no constraints found" << endl; return gensizeerr(contextptr); } vecteur obj(obj_orig); vector vartype(nv,(all_integer || all_binary) ? 1 : 0); vector vi=findvars(ivars,vars),vb=findvars(bvars,vars); for (vector::const_iterator it=vi.begin();it!=vi.end();++it) { vartype[*it]=1; } for (vector::const_iterator it=vb.begin();it!=vb.end();++it) { vartype[*it]=1; varsign[*it]=1; // binary variables are always nonnegative // append constraint of type xb<=1 m.push_back(midn(nv)[*it]); sv.push_back(-1); b.push_back(gen(1)); } vector vc=findvars(cvars,vars); for (vector::const_iterator it=vc.begin();it!=vc.end();++it) { vartype[*it]=0; } int nc=m.size(),ns=sv.size(); bool has_initp=use_affscale && !initp.empty(); if (has_initp) { if (int(initp.size())!=nv) return gendimerr(contextptr); initp=mergevecteur(initp,vecteur(ns,gen(1))); } m=mtran(m); // add slack/surplus variables matrice idn(midn(nc)); for (int i=nc-ns;i=0;--i) { if (varsign[i]==0) { m.insert(m.begin()+i,-*m[i]._VECTptr); obj.insert(obj.begin()+i,-obj[i]); vartype.insert(vartype.begin()+i,vartype[i]); if (has_initp) { if (is_positive(initp[i],contextptr)) { initp[i]+=gen(1); initp.insert(initp.begin()+i,gen(1)); } else { initp[i]=-initp[i]+gen(1); initp.insert(initp.begin()+i+1,gen(1)); } } } } m=mtran(m); nv=obj.size(); // solve the problem vecteur soln; gen optimum; if (!maximize) obj=-obj; if (use_affscale) { soln=initp; m=*_evalf(m,contextptr)._VECTptr; b=*_evalf(b,contextptr)._VECTptr; obj=*_evalf(obj,contextptr)._VECTptr; optimum=affscale(m,b,obj,soln,true,contextptr); } else { simplex_prepare(m,b,contextptr); simplex_twophase(m,b,obj,soln,optimum,contextptr); } if (soln.empty()) return soln; if (is_inf(_abs(optimum,contextptr))) return makevecteur(maximize?optimum:-optimum,vecteur(0)); if (!exactify_solution(soln,vartype,itol,contextptr)) { // apply branch & bound method vecteur isoln; gen ioptimum; node_count=0; cut_count=0; if (use_affscale) itol=_max(makesequence(gen(epsilon(contextptr)*100),itol),contextptr); branch_and_bound(m,b,obj,varsign,vartype,soln,isoln,ioptimum,itol,use_affscale,0,dl,nl,contextptr); cout << "branch and bound summary: " << node_count << " subproblems examined"; if (use_affscale) cout << endl; else cout << ", " << cut_count << " GMI cut" << (cut_count==1?"":"s") << " added" << endl; soln=isoln; optimum=ioptimum; if (soln.empty()) // no feasible integer solution return soln; } if (!is_exactly_zero(itol)) { soln=*_evalf(soln,contextptr)._VECTptr; optimum=_evalf(optimum,contextptr); } if (!maximize) optimum=-optimum; // recreate variables int i=0; soln.resize(nv); for (vector::const_iterator it=varsign.begin();it!=varsign.end();++it) { switch (*it) { case 0: soln[i+1]-=soln[i]; soln.erase(soln.begin()+i); break; case -1: soln[i]=-soln[i]; break; } ++i; } vecteur v=vars.empty()?soln:*_zip(makesequence(at_equal,vars,soln),contextptr)._VECTptr; if ((gv[0].type==_VECT && int(gv[0]._VECTptr->size())==0) || is_zero(gv[0])) return _simplify(v,contextptr); return makevecteur(_simplify(optimum+obj_ct,contextptr),_simplify(v,contextptr)); } static const char _lpsolve_s []="lpsolve"; static define_unary_function_eval (__lpsolve,&_lpsolve,_lpsolve_s); define_unary_function_ptr5(at_lpsolve,alias_at_lpsolve,&__lpsolve,0,true) #ifndef NO_NAMESPACE_GIAC } #endif // ndef NO_NAMESPACE_GIAC