Search code examples
optimizationclojureconstraint-programming

How to do basic optimisation using loco


I am trying to do a basic example of optimisation using loco.

I have a vector of costs the index of which corresponds to the integer value of a number of slots and would like to minimize the sum of the costs for a distinct subset of the slots.

Please see my attempt below, which fails to work because there is no "link" between the picked slots and the costs.

(def costs [10 10 20 20 30 30 40 40 10 10])

(let [slot-vars (for [i (range 5)] ($in [:slot i] 1 10))
      cost-vars (for [i (range 10)] ($in [:cost i] 10 40))]
  (solution
   (concat
    slot-vars
    cost-vars
    [($distinct (for [i (range 5)] [:slot i]))]
    (for [i (range 5)]
      ($= [:cost i] (get costs i))))
   :minimize (apply $+ (for [i (range 5)] [:slot i]))))

Solution

  • First, I'm not entirely sure I understand the point of this problem, because clearly the solution is to just take the 5 smallest values in the list. But if you want to make loco do it, I agree that the knapsack constraint is a handy tool for this. Contrary to what Mike said in his comment, I don't see any obstacle to using knapsack for minimization. Let the weights all be 1, and enforce that the weights add up to 5 in order to select 5 out of the 10 slots. I've used the variable [:include i] to indicate whether slot i should be included in the subset (1 for true and 0 for false). We want to minimize the dot product of the vector of :include variables and the cost vector.

    (def costs [10 10 20 20 30 30 40 40 10 10])
    (def weights (repeat 10 1))
    
    (def include-vars (for [i (range 10)] [:include i]))
    (def include-constraints (for [i (range 10)] ($in [:include i] 0 1)))
    
    (def model
      (concat
       include-constraints
       [($knapsack weights costs include-vars 5 :total)
        ($in :total 0 (apply + costs))]))
    
    (solution model :minimize :total)
    

    The result is:

    {[:include 4] 0, [:include 6] 0, [:include 9] 1, [:include 1] 1, [:include 3] 0, [:include 8] 1, :total 60, [:include 0] 1, [:include 7] 0, [:include 2] 1, [:include 5] 0}