/*

Set partition problem in ECLiPSe.

Problem formulation from
http://www.koalog.com/resources/samples/PartitionProblem.java.html
"""
This is a partition problem.
Given the set S = {1, 2, ..., n},
it consists in finding two sets A and B such that:
<ul>
<li>A U B = S,</li>
<li>|A| = |B|,</li>
<li>sum(A) = sum(B),</li>
<li>sum_squares(A) = sum_squares(B).</li>
</ul>
"""

Compare with the following models:
* MiniZinc: http://www.hakank.org/minizinc/set_partition.mzn
* Gecode/R: http://www.hakank.org/gecode_r/set_partition.rb
* Comet   : http://www.hakank.org/comet/set_partition.co

Model created by Hakan Kjellerstrand, hakank@gmail.com

Model simplified by Joachim Schimpf

*/

% Licenced under CC-BY-4.0 : http://creativecommons.org/licenses/by/4.0/

:-lib(ic).
:-lib(ic_sets).

% find all (7) solutions for N = 16.
go :-
N = 16,
NumSets = 2,
set_partition(N, NumSets, Sets, Sums),
writeln(sets:Sets), writeln(sums:Sums), nl,
fail.

%
% check for a solution between N = 17 and 32
%
go2 :-
ic : '::'(N,17..32),
indomain(N),
NumSets = 2,
writeln(n:N),
set_partition(N, NumSets, Sets, Sums),
writeln(sets:Sets), writeln(sums:Sums), nl.

%
% Here we find the minimal N and NumSets for
% a solution to the problem.
%
go3 :-
N #:: 2..20,
NumSets #:: 3..9,
indomain(N),
indomain(NumSets),
writeln([n:N,num_sets:NumSets]),
set_partition(N,NumSets).

set_partition(N, NumSets, Sets, [Sum,SumSquared]) :-

% create list of sets
intsets(Sets,NumSets,1,N),

% create the universe for partition_set
% and the weights for weight/3 below.
dim(Weights,[N]),
dim(Weights2,[N]),
( for(I,1,N), foreach(I,Universe),
foreacharg(I,Weights), foreacharg(W2,Weights2) do
W2 is I*I
),

% Sets must be a partition of the Universe
partition_set(Sets, Universe),

% all sets must have the same cardinality _C
( foreach(Set,Sets), param(_C) do
#(Set, _C)
),

% all sums and all squared sums must be equal
( foreach(Set,Sets), param(Weights,Weights2,Sum,SumSquared) do
weight(Set, Weights, Sum),
weight(Set, Weights2, SumSquared)
),

% symmetry breaking
[FirstSet|_] = Sets,
1 in FirstSet,

%
% search
%
label_sets(Sets).

%
% labeling the sets
%
label_sets([]).
label_sets([S|Ss]) :-
insetdomain(S,increasing,big_first,in_notin),
label_sets(Ss).

%
% Partitions the list of sets S into disjoint sets.
% All elements in the universe Universe must be
% included in exactly one of the sets.
%
partition_set(S, Universe) :-
all_disjoint(S),
all_union(S,Universe).