An Intro to Integer Programming for Engineers: Simplified Bus Scheduling
blog.remix.com
blog.remix.com
The documentation is hit or miss in some cases. MPSolver is pretty well documented. The more powerful constraint solver . . . well, I have no idea how to use the SA or Tabu search features, and minimal confidence that I'm doing anything correctly, except that it seems to emit correct-ish answers.
In the simplest implementation of Tabu Search, a maximum tabu list length of zero will never reject any candidates.
A simple simulated annealing should become a naive local search if the probability of rejecting a candidate solution is zero.
As far as which heuristic is better: I'd avoid premature optimization when solving optimization problems. Pick one, and if you're satisfied with the results, then go with it? Academics usually test their heuristics against benchmark problem instances with known optimal solutions (or if no optimal solution is known, best known upper bounds). That would be a bit more thorough.
If you'd like to talk optimization/metaheuristics feel free to shoot me an email (in my profile). I have a fair bit of experience with them so I might be able to point you in the right direction.
I'd reach out, but I doubt the company would approve of talking to a third party about the details of internal work. We also have some experts internally who I can talk to if I get stuck. :) Thank you for the kind offer, though.
However, describe this distinction to the average executive and tell us how it goes. :-)
However, I'm not sure the example chosen was the right introductory problem. As illustrated, we don't get the certifiably optimal solution because the true optimal solution may involve one of the duties we didn't randomly generate. If we don't get the optimal solution, why not just use a heuristic? Yes, there's a brief allusion to column generation which can address this problem, but surely there's an introductory problem which can be solved without requiring the user to think about column generation or similar?
Of course having found a couple of good combinations of duties with simulated annealing there's nothing stopping you from trying the integer program on those (+ variations) to try and find a better version.
Several Prolog systems ship with great implementations of both approaches.
I believe that it can be well suited for such problems—that is, that it is possible to write good solutions to them [0]—but isn't someone diving in without much experience likely just to write solutions that are age-of-universe slow because of combinatorial explosion?
[0] I don't know if it's available online anywhere, but Richard O'Keefe's "The craft of Prolog" (well worth reading for anyone interested in problem solving, programmer or not) has a gorgeous explanation of how to solve the problem "find a 9-digit number such that the `n`-digit initial [or maybe final?] segment is divisible by `n` for each `n` from 1 to 9", starting with the most naïve approach and progressing to a much more efficient one. It has a punchline that, for the benefit of anyone who hasn't read it, I won't spoil here.
For example, integer constraints are available under the name "constraint logic programming over finite domains", abbreviated as CLP(FD), in all most widely used Prolog implementations. To solve combinatorial optimization tasks with Prolog, I recommend to start with CLP(FD). If necesseary, you can always combine it with other approaches, such as applying the simplex algorithm and also custom methods and heuristics.
Please note that constraints have become widely available in Prolog systems only after the book you cite was published, and it does not mention these methods at all. With current systems, you can elegantly and efficiently solve the task using CLP(FD) constraints.
solution(Ds) :-
length(Ds, 9),
Ds ins 0..9,
foldl(segment_divisible(Ds), Ds, 1, _).
segment_divisible(Ds, _, I0, I) :-
I #= I0 + 1,
length(Finals, I0),
append(_, Finals, Ds),
reverse(Finals, RFs),
foldl(digits_number, RFs, 0-0, N-_),
N #= I0*_.
digits_number(D, I0-P0, I-P) :-
I #= I0 + D*10^P0,
P #= P0 + 1.
We can use it to generate solutions, for example: ?- solution(Ds), label(Ds).
Ds = [0, 0, 0, 0, 0, 0, 0, 0, 0] ;
Ds = [0, 0, 0, 0, 1, 2, 6, 0, 0] ;
Ds = [0, 0, 0, 0, 1, 5, 1, 2, 0] ;
Ds = [0, 0, 0, 0, 2, 7, 7, 2, 0] .
To exclude solutions where the leftmost digit is 0, we can simply impose an additional constraint on that digit. In addition, we are free to try different search strategies, completely decoupled from the constraints: ?- solution(Ds), Ds = [First|_], First #\= 0,
reverse(Ds, Rs), label(Rs),
portray_clause(Ds), false.
[9, 0, 0, 0, 0, 0, 0, 0, 0].
[8, 1, 0, 0, 0, 0, 0, 0, 0].
[7, 2, 0, 0, 0, 0, 0, 0, 0].
[6, 3, 0, 0, 0, 0, 0, 0, 0].
[5, 4, 0, 0, 0, 0, 0, 0, 0].
[4, 5, 0, 0, 0, 0, 0, 0, 0].
etc.
Constraints allow us to easily describe and efficiently such tasks. Note that this is very different from naive exhaustive search: The constraint solver automatically prunes those branches of the search space that cannot lead to a solution.In this concrete case, we get this by simply adding all_distinct(Ds) to the query. For example:
?- solution(Ds),
all_distinct(Ds),
reverse(Ds, Rs),
label(Rs).
This yields Ds = [6, 5, 4, 8, 7, 3, 1, 2, 0], in addition to other solutions. Now we only need to add that none of the integers must be 0. We can express this (for example) with the additional constraint Ds ins 1..9: ?- solution(Ds),
Ds ins 1..9,
all_distinct(Ds),
reverse(Ds, Rs),
label(Rs).
However, when you run this query, you get false in return. This means that there is no solution that satisfies all these constraints at the same time.Therefore, the task you mentioned was probably about the prefices, i.e., the initial segments of the number. We can easily express this with small modifications to the program above:
solution(Ds) :-
length(Ds, 9),
Ds ins 1..9,
all_distinct(Ds),
foldl(segment_divisible(Ds), Ds, 1, _).
segment_divisible(Ds, _, I0, I) :-
I #= I0 + 1,
length(Prefix, I0),
append(Prefix, _, Ds),
reverse(Prefix, RPs),
foldl(digits_number, RPs, 0-0, N-_),
N #= I0*_.
digits_number(D, I0-P0, I-P) :-
I #= I0 + D*10^P0,
P #= P0 + 1.
With this program, we get: ?- solution(Ds),
label(Ds).
Ds = [3, 8, 1, 6, 5, 4, 7, 2, 9] ;
false.
This means that there is a solution, and it is in fact unique! This is determined very efficiently thanks to the pruning that is automatically applied by the constraint solver.Prolog has some benefits in its more declarative nature, but it really constrains you because it forces you to use an entire prolog implementation, when all you really need is an interface to a solver.
For many cases, a Prolog system is already more than sufficient on its own, and is far more flexible than specialized tools which can still be provided as add-ons to Prolog. It is straight-forward to use only a specific subset of Prolog, if that is all you need. In contrast, it is generally impossible to use basic features of Prolog, such as backtracking and logical variables, in more specialized tools and solvers.
But for an understanding of the problem space and a good place to start trying to answer this unsolved problem.. this is still my favorite integer programming explanation:
> and i found this integer programming tutorial comprehensive in offering practical applications of the desired algorithm(i)
do any of these examples explain the problem best for you? can you write a program that will solve this question for you? now change the values of the variables, increase the number of variables does your algorithm still work on these other inputs?
can you rewrite the algorithm to give the correct answers for these other inputs?
can you develop enough coverage to prove all possible inputs return correct values?
if your coverage is substantial, seemingly complete, can you write against your algorithm to find inputs that will still require you to further develop the algorithm?
for me, i felt i was able to follow the explanation for how the author found the answer and through that was able to start to see the shape of the coverage necessary to accommodate such a problem (ii)
this specific tutorial is on integer programming.. one of karp's 21, and through reduction(iii) on our understanding of 0-1 integer programming we can understand all of the other 21
(o) 1972: Richard Karp: Reducibility Among Combinatorial Problems : http://www.cs.berkeley.edu/~luca/cs172/karp.pdf
(i) http://mat.gsia.cmu.edu/orclass/integer/integer.html
(ii) http://mat.gsia.cmu.edu/orclass/integer/node4.html
(iii) 1971: Stephen A. Cook: The Complexity of Theorem-Proving Procedures: http://4mhz.de/download.php?file=Cook1971_Letter.pdf
In fact, the code as shown only produces 48 trips, instead of the 52 given. I had to actually run it to make sure the Python interpreter in my had wasn't buggy. No idea what code they actually ran to get that result.