Integer vs. Linear Programming in Python
mlabonne.github.io
mlabonne.github.io
It's so easy to make a free blog on github (like sub 5 minutes) with some version of jekyl. You should probably just do it.
Solving for the funding plan of a bank to balance the balance sheets and meet all the regulatory constraints (it actually uses a linearisation of a piecewise linear convex problem that I had learned in college decades ago, but completely forgot about, I asked for guidance from one of my professors who asked for permission to use the case as an exercise for his students).
Assigning cash flows to changes in balances when the two feeds are inconsistent and the timing is off (you need to do some complex combinations to match them).
But same as OP, you get a sense of self-satisfaction but that won’t get you any credit internally.
The syntax demonstrated is very similar to the syntax of other libraries, including the python tools provided by Gurobi [1].
For experimentation and rapid prototyping LP, QP, MIP and geometric programming problems, I highly recommend CVXPY [2]. CVXPY allows the user to select a solver. Among the solvers is GLOP [3].
[1] https://www.gurobi.com/resource/modeling-examples-using-the-...
[2] http://cvxpy.org
[3] https://www.cvxpy.org/tutorial/advanced/index.html#choosing-...
I’ve often given up and just build the problems in python by outputting text files, which is very cumbersome.
I wonder whether there’s a better alternative, i.e. a python library that will deal well with large problems.
Nothing against python, but those models get unwieldy very quickly, especially when there are tons of overlapping constraints popping up.
Traditionally, however, mathematical models were encoded in domain-specific languages. The most prominent one is AMPL [4] which is proprietary. The glpk [5] people have developed a very neat open source clone of AMPL: the GNU MathProg language. For a more modern take on AMPL-type modelling DSLs, look at ZIMPL [6], which is open source as well.
[2] https://coin-or.github.io/pulp/
[3] https://jump.dev/JuMP.jl/stable/
[4] https://ampl.com
Take, for instance, the part of the general solution:
for r, _ in enumerate(RESOURCES):
solver.Add(sum(DATA[u][r] * units[u] for u, _ in enumerate(units)) <= RESOURCES[r])
the author is basically using enumerate to get the indices from the list, and nothing else. It seems like it would be much better to either do: for r, resource in enumerate(RESOURCES):
solver.Add(sum(DATA[u][r] * unit for u, unit in enumerate(units)) <= resource)
`enumerate` is supposed to return both the index and the value, so indexing the list you just passed to `enumerate` seems like bad form.Is there some reason not to do it this way?
The article makes it sound like they are guaranteed to find the optimum solution. However, the amount of iterations/execution time implies they don't simply use brute force.
So how do these solver guarantee an optimal solution? My first thought is that they somehow model the problem as an equation whose minima/maxima they can determine analytically but I'm unsure of how to build such an equation automatically (especially when multiple constraints like food, gold, wood etc. are involved).
Of course, for theoretical reasons, a heuristic is not guaranteed to be faster on all problem instances.
The general idea is to do some engineering and find a heuristic that works well for your problem. More sophisticated algorithms will try multiple heuristics at the same time (I believe OR tools has options that falll in this category). There is even research by now that aims to use ML to find good heuristics for a given family of problems (where the common characteristics of the problems might lend to a dominant heuristic).
These heuristics can still have guarantees of correctness. A good place to start is by looking up "branch and bound" algorithms, e.g., for ILP problems. The idea there is to narrow the search space while maintaining some theoretical guarantees.
(Not an expert but i did a project on this once. I'm sure someone more expert can come along and correct where I'm wrong.)
I think I have spotted some papers about this for combinatorial problems but not for continuous variables, which makes sense given their relative hardness.
Do you know some papers, journals, conferences about this?
I'm interested specially about the view from people working in optimization research more than in AI.
Integer programming is very hard, and is an active area of research. The space of problems that arise in practice typically have sparsity, special structure, and other features that allow for shortcuts to be employed so that brute force isn't needed.
Interior point solvers are guaranteed to find a global solution, and it can be proved that they will do so efficiently [1]. Practical implementations leverage decades of experience to improve upon the theoretical guarantees.
As it happens, MILP solvers often do use the simplex solver internally, as it can be hot-restarted - you can modify the problem a bit and quickly get an amended solution.
Yes, simplex is worst case exponential and barrier is worst-case polynomial, but depending on the problem, the average case characteristics are very different. Depending on the problem, simplex can beat barrier methods. For many large LP/MILP problems, heuristics and randomness usually dominate solution time rather than simplex vs barrier. That’s why commercial solvers so thoroughly beat open-source solvers —- their heuristics are far superior.
For NP-hard problems, it’s not a good idea to judge algorithms by their worst-case complexity —- which only provides bound — but by their real world performance. Before Karmarkar’s method, there was another polynomial time algorithm by Khachiyan. Despite being polynomial time, it was too slow to be of any practical interest.
It’s unfortunate they both share the same name, because they’re completely different algorithms.
Geometrically in R^n, the set of all points that satisfy the constraints is a high-dimensional polyhedron. The best points, the ones that minimize or maximize the linear objective function, are on the boundary of that polyhedron. There is either one (a vertex) or possibly infinitely many (a face), but in some standard reformulations (e.g. all variables are >= 0), there is always at least one optimal vertex. Optimal points can be found using iterative methods either going through the polyhedron ("interior point methods"), or going from vertex to vertex ("simplex method").
In theory, interior point methods can solve the problem to optimality in a time that is polynomial in the size of the input problem. All known variants of the simplex method are exponential in the worst case.
In practice, interior point methods are extremely fast and solve most problems in 10-100 iterations regardless of size! You can expect a problem with 100k variables and constraints to be solved within 5-20 minutes. However, using floating-point arithmetic, the solutions are not always very accurate, so it is customary to add some iterations of the simplex method at the end to get a vertex (vertices have a combinatorial structure; there is no accumulation of error, and one can get their value to high accuracy). On its own, the simplex method is still very fast, count 10-60 minutes for the same 100k-variable problem (with very high variance, though).
For integer programming (i.e. some or all of the variables are discrete):
This is much harder. The problem is NP-hard. In theory it looks hopeless. Indeed brute force is hopeless in practice.
But one can still to a lot better than brute force. IP solvers use branch-and-cut. The fastest ones (in which millions of PhD-holder hours have been poured) can solve problems with hundreds of thousands of variables in typically less than 24h. Of course, it is possible to construct a problem with 4 variables that will take years to solve, but that typically does not happen with naturally-occuring problems. For specially-structured problems (like knapsack, TSP or vehicle routing), customized codes can use special techniques to solve problems with tens of billions of variables.
I am always happy to see people point out that NP-hard problems are solved to optimality in practice. Too often you come across someone who believes it is impossible to prove you have an optimal solution for an NP-hard problems, no matter which instance. But NP-hardness does not rule out the possibility that you might be much more lucky than the worst case with the particular instance you are interested in. It only says that there exist worst case classes of instances that become much more difficult to solve as their size increases.
By the way, I remember having learned that Integer Programming with a constant number of variables but a variable number of constraints can actually be solved in polynomial time with help from the Lenstra–Lenstra–Lovász lattice basis reduction algorithm, so I would not expect an integer problem with four variables to need years to solve. A constant number of constraints is actually more difficult than a constant number of variables (the Knapsack Problem is NP-hard and has a single constraint). AFAIK, for a constant number of constraints we have nothing better than a pseudo-polynomial time algorithm by Papadimitriou.
Don't forget that you can also use LP relaxation ("well, what if we didn't need everything to be integral?"). If it comes out with integral weights (as was the case in the original example, modulo floating point error), then you're done as the sibling commenter points out. If you're willing to accept approximate solutions to your integer program, you can just pick a point near the "optimal" solution.
And, of course, modern IP solvers will use the relaxed LP problem to implement "cheap" upper bounds (assuming MAX, per standard form) for entire branches of the solution space, as the optimal solution to a less-constrained problem is never worse than for the more-constrained problem (since any feasible assignment remains so if you remove a constraint).
Any suggestion for good LP libraries/solvers for someone that isn't fond of Python? Isn't Prolog well suited for this kind of process?
For example, using Scryer Prolog and its CLP(ℤ) library for constraint logic programming over integers, we can solve the first optimization task from the article on the Prolog toplevel, without even having to define a predicate:
?- use_module(library(clpz)).
true.
?- Vs = [Swordsmen,Bowmen,Horsemen],
Vs ins 0..sup,
60*Swordsmen + 80*Bowmen + 140*Horsemen #=< 1200,
20*Swordsmen + 10*Bowmen #=< 800,
40*Bowmen + 100*Horsemen #=< 600,
Power #= Swordsmen*70 + Bowmen*95 + Horsemen*230,
labeling([max(Power)], Vs).
yielding solutions in decreasing amount of Power: Vs = [6,0,6], Swordsmen = 6, Bowmen = 0, Horsemen = 6, Power = 1800
; Vs = [7,1,5], Swordsmen = 7, Bowmen = 1, Horsemen = 5, Power = 1735
; Vs = [5,0,6], Swordsmen = 5, Bowmen = 0, Horsemen = 6, Power = 1730
; ... .
On backtracking, alternatives are automatically generated. The above interaction shows that in this concrete example, the optimal solution is unique. In Scryer Prolog, we can press "a" on the toplevel, and it will automatically enumerate all solutions, 558 in total for this example, ending with: ...,
; Vs = [2,0,0], Swordsmen = 2, Bowmen = 0, Horsemen = 0, Power = 140
; Vs = [0,1,0], Swordsmen = 0, Bowmen = 1, Horsemen = 0, Power = 95
; Vs = [1,0,0], Swordsmen = 1, Bowmen = 0, Horsemen = 0, Power = 70
; Vs = [0,0,0], Swordsmen = 0, Bowmen = 0, Horsemen = 0, Power = 0
; false.
If we omit the actual search for solutions from the query above, i.e., if we post only the part before labeling/2: ?- Vs = [Swordsmen,Bowmen,Horsemen],
Vs ins 0..sup,
60*Swordsmen + 80*Bowmen + 140*Horsemen #=< 1200,
20*Swordsmen + 10*Bowmen #=< 800,
40*Bowmen + 100*Horsemen #=< 600,
Power #= Swordsmen*70 + Bowmen*95 + Horsemen*230.
then we see from the answer that the constraint solver has automatically deduced, from the posted constraints, significantly restricted domains of the occurring integer variables: ...,
clpz:(Swordsmen in 0..20),
clpz:(Bowmen in 0..15),
clpz:(Horsemen in 0..6),
clpz:(Power in 0..4205).
This automatic restriction is called constraint propagation, and this is what reduces the search space so significantly. It also allows the application of heuristics that often significantly speed up up the search in practice, such as starting with domain variables with smallest domains, in the hope to detect infeasible cases earlier. We can flexibly select these heuristics as options of labeling/2, while leaving all other parts of the query the same. For instance, in this concrete example, I obtained a more than 2-fold speedup by using the "min" labeling option, i.e., writing labeling([max(Power),min], Vs), so that the search always selects a variable with smallest lower bound for branching.Constraint solvers blend in naturally in Prolog, and are often a reason for buying a fast commercial Prolog system. In fact, Prolog itself can be regarded as an instance of constraint programming: constraint logic programming over Herbrand terms, CLP(H), where (=)/2, i.e., terms are the same, and dif/2, i.e., terms are different, are the only constraints. Other specialized domains include CLP(B) over Boolean values, and CLP(Q) over the rational numbers, where dedicated algorithms allow very efficient and convenient solutions.
Constraint programming was also recently discussed in The Programming Paradigm to Find One Solution Among 8,080,104 Candidates:
Discrete Optimization https://www.coursera.org/learn/discrete-optimization
Solving Algorithms for Discrete Optimization https://www.coursera.org/learn/solving-algorithms-discrete-o...
The second course is the last part of a three part series. The first two parts are on modeling of problems with MiniZinc. They are also quite enjoyable.
https://medium.com/swlh/operations-research-with-r-graphical...
By the way, isn't that for-loop in section IV, point 2, iterating over resources, unnecessary? You're not using the loop variable r, and the constraint is on the minimum power of the army, which has nothing to do with the resources.
I often look at packages like this by use. Large, heavily utilized, corporate usage are indicative of a healthy project to me.
What metrics do other HN readers use for evaluating projects like this?
What did you end up using as alternative? In C++ for example, there's a template based library (Eigen) that fuses multiple operations and gives you some speedup compared to the usual libraries, at the cost of very long compilation times. For C#, I imagine there could be some library that does the same thing in JIT instead.
[1] https://www.nag.com/numeric/dt/nagdotnet_dtw02/html/contents...
These days, it survives mostly as an authoring language for math and statistical libraries.
The issue is, MILP in general is NP-complete to solve... so it doesn’t gain you anything. In practice solutions can be found quickly, or at least good approximations.