change PuLP's (for Python) constraint tolerance - python

I am using the PuLP linear programming module for Python to solve a linear problem.
I set up the problem, the constraints, and I use the default solver provided with PuLP which is CBC (the solver executable on my mac is called cbc-osx-64 for obvious reasons). When running this executable:
Welcome to the CBC MILP Solver
Version: 2.7.6
Build Date: Mar 3 2013
Revision Number: 1770
OK, I run the solver via PuLP and get a solution. When verifying that the constraints are satisfied I get a difference between the solution and what I requested (for some of the constraints, not all), which is less than 1e-6 but greater than 1e-7 (1.6e-7, e.g.).
Of course it makes sense to have a constraint tolerance, that is fine. But I need to be able to control this and I think this should be a very central and important parameter in any LP task?
So let us look at the "help" from the CBC solver (run the executable and type "?"), these are the arguments I can change:
Commands are:
Double parameters:
dualB(ound) dualT(olerance) primalT(olerance) primalW(eight) zeroT(olerance)
Branch and Cut double parameters:
allow(ableGap) cuto(ff) inc(rement) integerT(olerance) preT(olerance)
pumpC(utoff) ratio(Gap) sec(onds)
Integer parameters:
force(Solution) idiot(Crash) maxF(actor) maxIt(erations) output(Format)
slog(Level) sprint(Crash)
Branch and Cut integer parameters:
cutD(epth) cutL(ength) depth(MiniBab) hot(StartMaxIts) log(Level) maxN(odes)
maxS(olutions) passC(uts) passF(easibilityPump) passT(reeCuts) pumpT(une)
strat(egy) strong(Branching) trust(PseudoCosts)
Keyword parameters:
allC(ommands) chol(esky) crash cross(over) direction error(sAllowed)
fact(orization) keepN(ames) mess(ages) perturb(ation) presolve
printi(ngOptions) scal(ing) timeM(ode)
Branch and Cut keyword parameters:
clique(Cuts) combine(Solutions) combine2(Solutions) cost(Strategy) cplex(Use)
cuts(OnOff) Dins DivingS(ome) DivingC(oefficient) DivingF(ractional)
DivingG(uided) DivingL(ineSearch) DivingP(seudoCost) DivingV(ectorLength)
feas(ibilityPump) flow(CoverCuts) gomory(Cuts) greedy(Heuristic)
heur(isticsOnOff) knapsack(Cuts) lagomory(Cuts) lift(AndProjectCuts)
local(TreeSearch) mixed(IntegerRoundingCuts) node(Strategy)
pivotAndC(omplement) pivotAndF(ix) preprocess probing(Cuts)
rand(omizedRounding) reduce(AndSplitCuts) residual(CapacityCuts) Rens Rins
round(ingHeuristic) sos(Options) two(MirCuts) Vnd(VariableNeighborhoodSearch)
Actions or string parameters:
allS(lack) barr(ier) basisI(n) basisO(ut) directory dualS(implex)
either(Simplex) end exit export gsolu(tion) help import initialS(olve)
max(imize) min(imize) para(metrics) primalS(implex) printM(ask) quit
saveS(olution) solu(tion) stat(istics) stop
Branch and Cut actions:
branch(AndCut) doH(euristic) prio(rityIn) solv(e)
The values of these parameters have values:
dualTolerance has value 1e-07
primalTolerance has value 1e-07
zeroTolerance has value 1e-20
allowableGap has value 0
integerTolerance has value 1e-06
preTolerance has value 1e-08
ratioGap has value 0
The only parameter which could be associated with the constraint tolerance and also consistent with my observations is the "integerTolerance".
So, I changed this tolerance to 1e-8 but got the same result (that is, the solution differed from the ground truth by more than 1e-7).
Questions:
Can anyone shed some light on this? In particular, is there a way to set the constraint tolerance (the difference between a found solution and what we requested)?
If not for CBC, do you know of any other solver (GLPK, Gurobi, etc.) where this quantity can be set?
Thanks.

At least in the latest pulp Version you can set it directly via parameter.
https://pythonhosted.org/PuLP/solvers.html
Parameter fracgap should do and does for me.

I can't give you an exact answer, but I'd try primal or dual tolerance. Integer tolerance doesn't make sense for constraints to me.
Do you know how to change these options via the Python interface (I would like to experiment with it, but don't want to call the command line tool, and I'm not able to pass options to the solver) ?

Related

Python CPLEX warm starts from infeasible solution

I currently have an (integer) LP problem solved which has, amongst others, the following mathematical constraint as pseudocode.
Packages_T1 + Packages_T2 + Packages_T3 + RPackages = 25
It represents three package trucks (T1, T2 and T3) to each of which packages can be assigned plus a residual/spilled package variable which is used in the objective function. The current value of 25 represents the total package demand.
Lets say I want to re-solve this problem but change the current demand of 25 packages to 35 packages. When I warm start from the previous solution with 25 packages CPLEX errors out in stating that the provided solution is infeasible: which makes perfect sense. However, it subsequently fails to repair the previous solution even though the most straight-forward way to do this would be to "up" the RPackages variable for each of these constraints.
My question is whether there is any possibility to still use the information from the previously solved problem as a warm start to the new one. Is there a way to, for example, drop all RPackages from the solution and have them recalculated to fit the new constraint right-hand side? A "last resort" effort I thought of would be to manually recalculate all these RPackages values myself and replace them in the old solution but a more automated solution to this problem would be preferred. I am using the standard CPLEX Python API for reference.
Thank you in advance.
even if the warmstart is not feasible CPLEX can use some information.
Let me use the zoo example from
https://www.linkedin.com/pulse/making-optimization-simple-python-alex-fleischer/
from docplex.mp.model import Model
mdl = Model(name='buses')
nbbus40 = mdl.integer_var(name='nbBus40')
nbbus30 = mdl.integer_var(name='nbBus30')
mdl.add_constraint(nbbus40*40 + nbbus30*30 >= 300, 'kids')
mdl.minimize(nbbus40*500 + nbbus30*400)
warmstart=mdl.new_solution()
warmstart.add_var_value(nbbus40,4)
warmstart.add_var_value(nbbus30,0)
mdl.add_mip_start(warmstart)
sol=mdl.solve(log_output=True)
for v in mdl.iter_integer_vars():
print(v," = ",v.solution_value)
gives
Warning: No solution found from 1 MIP starts.
Retaining values of one MIP start for possible repair.

How can I retrieve the current best solution from the Gurobi solver when using PulP?

If I use the interactive solver in the gurobi solver, I can do the following:
gurobi> m = read('model.mp')
gurobi> m.optimize()
[...]
Found heuristic solution: objective 821425.00000
Then abort and get the current solution via
gurobi> m.printAttr('X')
I want to have the same behavior in pulp. In particular, after having called:
prob = pulp.LpProblem(name="MIPProblem", sense=pulp.LpMaximize)
[...]
status = prob.solve(pulp.GUROBI_CMD(msg=True, keepFiles=1))
I want to wait until the first heuristic solution is found/abort after a certain timespan and then obtain the current best solution found by Gurobi. How would I do that?
You can either use pulp.GUROBI or pulp.GUROBI_CDM.
The main difference is that pulp.GUROBI is a wrapper to gurobipy (the Gurobi Python Interface) while pulp.GUROBI_CDM uses the command line (i.e, it writes the LP/ILP in a file and then calls the solver).
Let's distinguish the two cases:
Case 1: pulp.GUROBI:
In this case you can access the solverModel object. For the fields you can refer directly to the documentation. However, for your specific use case what you're looking for is ObjBound.
A Small Example:
import pulp
...
status = prob.solve(pulp.GUROBI(timeLimit=1))
print(pulp.LpStatus[status]) # status
print(prob.solverModel.ObjBound) # best objective found
Case 2: pulp.GUROBI_CDM:
In this case, the problem is solved by command line and the solution is read from the result file (i.e., here)
import pulp
...
status = prob.solve(pulp.GUROBI_CMD(options=[('TimeLimit','1')]))
print(pulp.LpStatus[status]) # status
print(pulp.value(prob.objective)) # best objective found
Note that, GUROBI_CMD does provide good solution status [see here], so you may read Optimal even though the solution is not since no information about the status are provided by the Gurobi solution file.

Function definition and function call produce syntax error in python 3, even when copied and pasted

I am trying to solve the problem of a least squares fit of a power law spliced to a third order polynomial in python using gradient descent. I have computed gradients with respect to the parameters in Matlab. The boundary conditions I computed by hand. I am running into a syntax error in my chi-squared minimization algorithm, which must take into account the boundary conditions. I am doing this for a machine learning class in which I am completing a somewhat self-directed and self-proposed long term project, but I am stuck because of this syntax error that I am not sure how to overcome. I will not get class credit for this. It is simply something to put on my resume.
def polypowerderiv(x,a1,b1,c1,a2,b2,c2,d2,boundaryx,ydat):
#need to minimize square of ydat-polypower
#from Mathematica, to be careful
gradd2=2*(d2+c2*x+b2*x**2+a2*x**3-ydat)
gradc2=gradd2*x
gradb2=gradc2*x
grada2=gradb2*x
#again from Mathematica, to be careful
gradc1=2(c+a1*x**b1-ydat)
grada1=gradc1*x**b1
gradb1=grada1*a1*log(x)
return [np.sum(grada1),np.sum(gradb1),\
np.sum(gradc1),np.sum(grada2),np.sum(gradb2),\
np.sum(gradc2),np.sum(gradd2)]
def manualleastabsolutedifference(xdat, ydat, params,seed, maxiter, learningrate):
chisq=0 #chisq is the L2 error of the fit relative to the ydata
dof=len(xdat)-len(params)
xparams=seed
for step in np.arange(maxiter):
a1,b1,c1,a2,b2,c2,d2=params
chisq=polypowerlaw(xdat,params)
for i in np.arange(len(xdat)):
grad=np.zeros(len(seed))
for i in np.arange(seed):
polypowerlawboundarysolver=\
polypowerboundaryconstraint(xdat,a1,b1,c1,a2,b2,c2)
boundaryx=minimize(polypowerlawboundarysolver,x0=1000)
#hard coded to be half of len(xdat)
chisq+=abs(ydat-\
polypower(xdat,a1,b1,c1,a2,b2,c2,d2,boundaryx)
grad=\
polypowerderiv(xdat,a1,b1,c1,\
a2,b2,c2,d2,boundaryx,ydat)
params+=learningrate*grad
return params
The error I get is:
File "", line 14
grad=polypowerderiv(xdat,a1,b1,c1,a2,b2,c2,d2,boundaryx,ydat)
^
SyntaxError: invalid syntax
Also, I'm having some small trouble with formatting. Please help. This one of my first few posts to Stack Overflow ever, after many years of up and down votes. Thank you for your extensive help, community.
As per Alan-Fey, you forgot a closing bracket:
chisq+=abs(ydat-\
polypower(xdat,a1,b1,c1,a2,b2,c2,d2,boundaryx)
should be
chisq+=abs(ydat-\
polypower(xdat,a1,b1,c1,a2,b2,c2,d2,boundaryx))

Long Vector Linear Programming in R?

Hello and thanks in advance. Fresh off the heels of this question I acquired some more RAM and now have enough memory to fit all the matrices I need to run a linear programming solver. Now the problem is none of the Linear Programming packages in R seem to support long vectors (ie large matrices).
I've tried functions Rsymphony_solve_LP, Rglpk_solve_LP and lp from packages Rsymphony, Rglpk, and lpSolve respectively. All report a similar error to the one below:
Error in rbind(const.mat, const.dir.num, const.rhs) :
long vectors not supported yet: bind.c:1544
I also have my code below in case that helps...the constraint matrix mat is my big matrix (7062 rows by 364520 columns) created using the package bigmemory. When I run this this line the matrix is pulled into memory and then after a while the errors show.
Rsymph <- Rsymphony_solve_LP(obj,mat[1:nrow(mat),1:ncol(mat)],dir,rhs,types=types,max=max, write_lp=T)
I'm guessing this is a hard-coded error in each of the three functions? Is there currently a linear programming solver in R or even Python that supports long vectors? Should I contact the package maintainers or just edit the code myself? Thanks!
The package lpSolveAPI can solve long-vector linear programming problems. You have to first start my declaring a Linear Programming object, then add the constraints:
library(lpSolveAPI)
#Generate Linear Programming Object
lprec <- make.lp(nrow = 0 # Number of Constraints
, ncol = ncol(mat) # Number of Decision Variables
)
#Set Objective Function to Minimize
set.objfn(lprec, obj)
#Add Constraints
#Note Direction and RHS is included along with Constraint Value
for(i in 1:nrow(mat) ){
add.constraint(lprec,mat[i,], dir[i], rhs[i])
print(i)
}
#Set Decision Variable Type
set.type(lprec, c(1:ncol(mat)), type = c("binary"))
#Solve Model
solve(lprec)
#Obtain Solutions
get.total.iter(lprec)
get.objective(lprec)
get.variables(lprec)
There's a good introduction to this package here.

Python - Scipy : ode module : issue enabling the step option of the solver

I wanted to store the different integration steps taken by the solver itself when I call it :
solver1.integrate(t_end)
So I did a while loop and enabled the step option setting its value to True:
while solver1.successful() and solver1.t < t0+dt:
solver1.integrate(t_end,step=True)
time.append(solver1.t)
Then I plot y, the result of integration and here comes my issue. I have instabilities which appear in a located area :
I thought it was because of the loop or something like that so I checked the result removing the step :
while solver1.successful() and solver1.t < t0+dt:
solver1.integrate(t_end)
And surprise ... I have the correct result :
It's a quite weird situation ... I'd be grateful if someone of your guys could help me with this issue.
EDIT :
To set the solver I do :
solver1 = ode(y_dot,jac).set_integrator('vode',with_jacobian=True)
solver1.set_initial_value(x0,t0)
And I store the result using .append()
When you set step=True you are indirectly giving the vode._integrator.runner (a Fortran subroutine) an instruction to use itask=2, and the default is itask=1. You can get more details about this runner doing:
r._integrator.runner?
In SciPy 0.12.0 documentation you will not find an explanation about what is going on for the different itask=1 or itask=2, but you can find it here:
ITASK = An index specifying the task to be performed.
! Input only. ITASK has the following values and meanings.
! 1 means normal computation of output values of y(t) at
! t = TOUT(by overshooting and interpolating).
! 2 means take one step only and return.
! 3 means stop at the first internal mesh point at or
! beyond t = TOUT and return.
! 4 means normal computation of output values of y(t) at
! t = TOUT but without overshooting t = TCRIT.
! TCRIT must be input as RUSER(1). TCRIT may be equal to
! or beyond TOUT, but not behind it in the direction of
! integration. This option is useful if the problem
! has a singularity at or beyond t = TCRIT.
! 5 means take one step, without passing TCRIT, and return.
! TCRIT must be input as RUSER(1).

Categories