Download
:- use_module(library(lists)).

% Number Partitioning (CSPLib prob049)
% Split the numbers 1..N into two sets A and B of equal size such that
% sum(A) = sum(B) and the sum of squares of A equals that of B.

% partition(+N, -A, -B)
partition(N, A, B) :-
    0 is N mod 2, % equal-sized sets require an even N
    Total is N * (N + 1) // 2, % sum of 1..N
    0 is Total mod 2, % the sum must be splittable in half
    HalfSum is Total // 2,
    TotalSq is N * (N + 1) * (2*N + 1) // 6, % sum of squares of 1..N
    0 is TotalSq mod 2, % the sum of squares too
    HalfSq is TotalSq // 2,
    Half is N // 2, % target size of each set
    N1 is N - 1,
    numlist(1, N1, Pool), % candidates still to assign: 1..N-1
    NSq is N * N,
    % symmetry break: the largest number N is placed in set A up front
    place(Pool, HalfSum, HalfSq, Half, [N], N, NSq, 1, A0),
    sort(A0, A), % set A in ascending order
    numlist(1, N, All),
    subtract(All, A, B). % set B is everything not in A

% place(+Remaining, +HalfSum, +HalfSq, +Half, +AccA, +SumA, +SqA, +SizeA, -A)
% Decide set membership for each remaining number. Only set A is tracked:
% once A has hit every target, B is forced to match (the grand totals are fixed).

% base case: no numbers left -- A must have hit all three targets exactly
place([], HalfSum, HalfSq, Half, AccA, HalfSum, HalfSq, Half, AccA).
% option 1: put X into set A
place([X|Xs], HalfSum, HalfSq, Half, AccA, SumA, SqA, SizeA, A) :-
    SumA1 is SumA + X, SumA1 =< HalfSum, % must not overshoot the sum
    SizeA1 is SizeA + 1, SizeA1 =< Half, % must not overshoot the size
    SqA1 is SqA + X * X, SqA1 =< HalfSq, % must not overshoot the squares
    place(Xs, HalfSum, HalfSq, Half, [X|AccA], SumA1, SqA1, SizeA1, A).
% option 2: put X into set B (simply leave it out of A)
place([_|Xs], HalfSum, HalfSq, Half, AccA, SumA, SqA, SizeA, A) :-
    place(Xs, HalfSum, HalfSq, Half, AccA, SumA, SqA, SizeA, A).