Minimize your cloud costs with GLPK and Haskell
chrisstucchio.com
chrisstucchio.com
The literate Haskell version on Github:
https://github.com/stucchio/Cloud-capacity-planning/blob/mas...
http://www-sop.inria.fr/members/Remigiusz.Modrzejewski/prese...
Note this presentation was given to an audience that mostly knew ILP and has used it before for combinatorial optimization, so I'm not gentle with theory. It's just to show that Python is a little nicer than Java to do it.
Then there are things like
map (\(x,y) -> x) reservationFixedCosts
instead of
map fst reservationFixedCosts
I'm also not a big fan of extensive use of list comprehensions; I'd prefer using "map" instead wherever I can.
The whole thing could definitely look much cleaner.
However, Python code to solve the same problem would probably look very similar to the Haskell code. The initial example given in that presentation has only 4 variables and 4 constraints (slide 5), I have 15 and 27.
Once you get to bigger problems, python code winds up looking similarly ugly. See lines 14-15 in that presentation. As the problems get bigger, your code isn't representing the LP problem. It's translating a small problem specification into a much bigger LP problem.
So no need for the LpVariable stuff, for example, and you get type safety.
An example of an SMT solver DSL, which:
* reuses Haskell's type checker to check the types of the embedded language,
* uses Haskell's polymorphism to add polymorphism to the underlying language
* and the host language's variables as target language variables.
http://code.haskell.org/~dons/docs/yices-painless/Yices-Pain...
Linear Programming is one of those things that's usually underrated (and sometimes overrated)
But I would probably not use Haskell and just create the problem as a text file in whatever format GLPK accepts - probably a derivative of 'the standart format' (which is bad for very complex problems, still)
I'd write the program anyway, however - the text file would actually be quite large. This problem has 15 variables and 27 constraints (if I counted correctly). Your plain text input to glpk would be at least 43 lines long (counting the objective function), compared to about 30 lines of haskell.
If your usage pattern varied daily (e.g., one pattern for monday, a different one for tuesday), your input would become even bigger. The only modification to the haskell code would be:
main = do
printLPSolution (Map.fromList [ ("mon_0", 12.2), ("mon_1", 25.1), ("mon_2", 53.5),
("tues_0", 15.8), ("tues_1", 33.9), ("tues_2", 76.2),
...
])Here's the docs for the language glpk uses
http://www.cs.unb.ca/~bremner/docs/glpk/gmpl.pdf
Yeah, with 15 (effective) variables it's better to write in some higher level way.
And there's always the canonical way, but that's awful to write by hand, like
k00.v00 + K01.v01 + K02.v02 + k03.v03 < C0 k10.v10 + K11.v11 + K12.v12 + k13.v13 < C1
I'd be very surprised if it was as easy to set up the problem in R or Macsyma. In those languages, you'd probably need to write loops to generate your constraints (but maybe I'm wrong, I'm no R-expert).
But I'd love to see code proving me wrong.
So you'd simply do Minimize[function, {variables/constraints}] and then get on with your day.
lp = execLPM $ do setDirection Max
setObjective objFun
leqTo (varSum ["x1", "x2", "x3"]) 100
leqTo (10 *^ var "x1" ^+^ 4 *& "x2" ^+^ 5 *^ var "x3") 600
leqTo (linCombination [(2, "x1"), (2, "x2"), (6, "x3")]) 300
varGeq "x1" 0
varBds "x2" 0 50
varGeq "x3" 0
setVarKind "x1" IntVar
setVarKind "x2" ContVar
main = print =<< glpSolveVars mipDefaults lp
The code in the blog post is just longer and more complicated because it's generating multiple constraints from code.The only reason my code doesn't look as simple as the above is because it's doing a lot more. For example, this line generates 9 constraints:
reservationConstraints loadPattern = [ linCombination [ (1.0, "reservation_" ++ k),
(-1.0, "reserved_" ++ k ++ "_" ++ p) ], 0.0 |
p <- (Map.keys loadPattern),
k <- reservationTypes ]
E.g.: reservation_heavy - reserved_heavy_morning <= 0
reservation_heavy - reserved_heavy_afternoon <= 0
reservation_heavy - reserved_heavy_evening <= 0
reservation_medium - reserved_medium_evening <= 0
....
If Mathematica is so easy, I'd love to see a significantly simpler version of the same problem written in it. But I have absolutely no idea how I'd write that simpler version.Example:
Minimize[{x + 2 y, -5 x + y == 7 && x + y >= 26 && x >= 3 && y >= 4}, {x, y}] LinearProgramming[{1, 2}, {{-5, 1}, {1, 1}}, {{7, 0}, {26, 1}}, {{3, Infinity}, {4, Infinity}}]
To demonstrate mathematica is easier, you need to demonstrate that it's similarly easy to translate the problem from a verbal specification to an LP problem. Solving LP is the easy part.
I tend to use clp these days but glpk is a good LP solver as well.