For those who thought that the problem presented in On optimizing garden production (simple), here is an example which presents a bit more of a problem, even for a computer.

% second garden model array[int] of int: rpp; array[int] of int: cpp; int: np = length(rpp); constraint assert(length(rpp)=length(cpp),"Input arrays unbalanced"); set of 1..np: Splants = 1..np; array[Splants] of var 0..5: plants; var int: profits; constraint forall(p in Splants)(plants[p] >= 0); constraint sum(plants) = 17; constraint profits = sum([plants[p]*(rpp[p]-cpp[p]) | p in Splants]); solve maximize profits; output ["\(profits) \(plants)"];

This model is almost the same as the previous toy example, but has some significant differences.

In this case we have two input arrays, revenue per plant or variety (rpp) and cost per variety (cpp). The profit is just the net difference between revenue and cost. Separating out the two components allows us to focus on determining the true values of each without influence from the other. The data is stored in a different file, allowing us to change the data while leaving the model intact. When data changes, we just re-run the model.

It is important to verify the integrity of the incoming data, so we allow MiniZinc to determine how many elements are in the revenues array and assert that the costs array has to be the same length. We also set up an array for calculating how much of each variety we want to produce, and set it to allow the optimizer to choose integer values between none and five. The limit of five might come from market research which shows that the market will absorb only 5 units.

Again, we ensure that the optimizer does not consider negative values for our production numbers, assume that we cannot produce more than 17 units due to space limitations, and calculate our profits based on the production numbers multiplied by the profits, that is, the difference between revenue and cost for each item.

Given the following data, which is an example for 30 different varieties

rpp = [1,2,4,3,6,4,5,3,2,4,6,7,1,5,2,1,5,6,2,3,7,9,2,4,6,2,3,1,2,5]; cpp = [2,3,2,1,4,6,8,2,3,4,1,3,2,2,1,2,2,3,3,5,3,4,7,2,2,1,1,3,2,1];

... we find that the computer considers the problem carefully and quite quickly arrives at a suggestion of a total profit of 78. However it takes more than four minutes to satisfy itself that there are no better solutions available. This is because while the model has been expressed so that it is readable, it is not very efficient. One way to improve efficiency is to change the constraint which calculates profits to:

constraint profits = let { array[Splants] of int: ppp = [rpp[p]-cpp[p] | p in Splants] } in sum([plants[p] * ppp[p] | p in Splants where ppp[p] > 0]);

This change reduces the time to calculate the optimal answer to 22 seconds. This different constraint uses a local variable in the "let" construction and only sums over the items where we know that profit per plant is better than zero. There are still more improvements possible. This illustrates that in building models we need to continuously improve the way the problem is expressed and the model written to make things easier for the solver. Now that the model is faster, we can either consider adding more plant variety alternatives or consider more practical constraints related to temperature and so on.

With an idea of the maximum profit available, we can change the model to find out if there are alternate solutions which give the same overall profit. By constraining the variable profits to 78 and changing the solve maximize statement to solve satisfy, we find that there are 104 equivalent solutions to the problem given this data, involving different combinations of the plant varieties. This suggests that we can look more closely at those plants considered, perhaps eliminate those never chosen, or add other constraints to anticipate issues related to practical growing factors such as timing and temperature.

Copyright © 2016