This is a simplified example; say I want to distribute weights over tables such that the tables end up roughly equally loaded, e.g. (10 10 10 5 5) Kg can balance equally as (10 10) (10 5 5).
(10 9 8 7) can't balance so I prefer (10 7) (9 8) or (10 9) (8 7) to (10 9 8) (7). i.e. closer to a balanced weight distribution is the goal. NB. balancing total load on each table, not quantity of weights on each table (I think that rules out global_cardinality).
I have tried to sum the total input weights and constrain "each table must have #=< Total//2". Inputs won't balance exactly so if one has to have slightly more than half, that fails. Adding some wiggle room "#=< (Total//2) + Tolerance" and if the Tolerance is too small then no answers, if it's too large instead of balancing as equally as it can, it stops as soon as one is just under the cutoff - as unbalanced as it can get away with. Trying to label with Tolerance in 1..1000 and min(Tolerance) feels like it should work to get closest to balance, but I haven't got it to - mostly getting non-termination in minutes of waiting.
Here's my code for one trial, e.g. for answers weight(10)-2 is a 10Kg weight paired with table 2:
% weight(Kg)
weight(10).
weight(10).
weight(10).
weight(10).
% balance weight-table pairs, Table 1 total KG, Table 2 total Kg
balance([], 0, 0).
balance([weight(Kg)-1 | Ws], Table1Total, Table2Total) :-
Table1Total #= T1_ + Kg,
balance(Ws, T1_, Table2Total).
balance([weight(Kg)-2 | Ws], Table1Total, Table2Total) :-
Table2Total #= T2_ + Kg,
balance(Ws, Table1Total, T2_).
go(Pairs) :-
findall(weight(Kg), weight(Kg), Weights),
pairs_keys_values(Pairs, Weights, Tables),
Tables ins 1..2,
balance(Pairs, T1, T2),
TableDiff #= abs(T2-T1), % seek to minimise the difference, doesn't seem to do that?
labeling([min(TableDiff)], [TableDiff|Tables]).
I expect to see it assign two weights to each table since these weights do have an even balance:
% expected / desired:
?- go(P).
P = [weight(10)-1, weight(10)-1, weight(10)-2, weight(10)-2]
% actual:
?- go(P).
P = [weight(10)-1, weight(10)-1, weight(10)-1, weight(10)-1] % all on table 1
That actual result put all weights (40Kg) on Table1 and none on Table2. It doesn't seem to be seeking min(TableDiff) at all. https://www.swi-prolog.org/pldoc/man?predicate=labeling/2 says:
The order of solutions can be influenced with:
min(Expr)
max(Expr)
This generates solutions in ascending/descending order with respect to the evaluation of the arithmetic expression Expr.
I have tried with putting the equation in the min(abs(T2-T1)) as well with no change. How can I get good balanced solutions, use clpfd to seek a max or min? (related: the balance/3 predicate is ugly, is there a nicer way to use clpfd with aggregate or group_by groupings?)
Not a nice solution but it can be done this way:
It assumes the weights are non-negative and some solutions are missing without the permutation (it's greedy).
Example: