How count Dijkstra guards sort executions in Prolog - sorting

I do not find a Prolog cut in Dijsktras "if fi", since he says "otherwise an arbitrary guarded list with a true guard will be selected for execution.". So his construct, does not choose the first match, as a Prolog cut would do:
if Cond1 -> Action11, .., Action1n1
[] Cond2 -> Action21, .., Action2n2
...
[] Condm -> Actionm1, .., Actionmn2
if
Is there maybe a Prolog cut in the "do od" construct, which loops as long as at least one condition of a guarded list is true? Or maybe some other approach to realize it in Prolog, I assume a loop can be translated to a recursive call. So how would we do this sorting of q1,q2,q3,q4 in Prolog:
do q1 > q2 -> q1,q2 := q2,q1
[] q2 > q3 -> q2,q3 := q3,q2
[] q3 > q4 -> q3,q4 := q4,q3
od
How many non-deterministic execution paths aka Prolog solutions will the Prolog program have for input 7,11,5,3 that all provide the same answer?

I think you can do something like this:
do([Q1,Q2,Q3,Q4], [swap(Q1,Q2)|P], S) :-
Q1 > Q2,
do([Q2,Q1,Q3,Q4], P, S).
do([Q1,Q2,Q3,Q4], [swap(Q2,Q3)|P], S) :-
Q2 > Q3,
do([Q1,Q3,Q2,Q4], P, S).
do([Q1,Q2,Q3,Q4], [swap(Q3,Q4)|P], S) :-
Q3 > Q4,
do([Q1,Q2,Q4,Q3], P, S).
do([Q1,Q2,Q3,Q4], [], [Q1,Q2,Q3,Q4]) :- % termination state
Q1 =< Q2,
Q2 =< Q3,
Q3 =< Q4.
Execution:
?- do([7,11,5,3],P,S).
P = [swap(11, 5), swap(7, 5), swap(11, 3), swap(7, 3), swap(5, 3)],
S = [3, 5, 7, 11] ;
P = [swap(11, 5), swap(11, 3), swap(7, 5), swap(7, 3), swap(5, 3)],
S = [3, 5, 7, 11] ;
P = [swap(11, 5), swap(11, 3), swap(5, 3), swap(7, 3), swap(7, 5)],
S = [3, 5, 7, 11] ;
P = [swap(5, 3), swap(11, 3), swap(7, 3), swap(11, 5), swap(7, 5)],
S = [3, 5, 7, 11] ;
P = [swap(5, 3), swap(11, 3), swap(11, 5), swap(7, 3), swap(7, 5)],
S = [3, 5, 7, 11] ;
false.

If we assume no guard calculation can diverge, I think the following expresses the same computation as the sort example from the question:
dgsort([A,B,C,D],R):-
( A > B -> X1 = [1] ; X1 = [] ),
( B > C -> X2 = [2|X1] ; X2 = X1 ),
( C > D -> X3 = [3|X2] ; X3 = X2 ),
( X3 = [] -> R = [A,B,C,D]
; random_member( X, X3),
( X =:= 1 -> dgsort([B,A,C,D],R)
; X =:= 2 -> dgsort([A,C,B,D],R)
; X =:= 3 -> dgsort([A,B,D,C],R) )).
comments: "an arbitrary guarded list with a true guard will be selected for execution" and all the others dropped, i.e. the chosen one is committed to, I always thought. Thus if I had to implement it I'd spawn all the branches's guards, check on them at some timeout intervals, and as soon as one or more are successful I'd pick one of them randomly, kill all the rest, and proceeded with the winner. i.e. committing to it. that is my understanding anyway. (or maybe the timeout should be random as well?)
I also thought the do-od construct chose the true guard by the same mechanism. i.e. "at least one" and we don't care which. If we assume all the guards calculations terminate, we can just execute all of them, pick the winner randomly, then proceed with its actions. Which is what the code above is doing.
To find out the number of execution paths we need to change random_member to just member, run the predicate through findall and measure the length of the resulting list:
dgsortn([A,B,C,D],R):-
( A > B -> X1 = [1] ; X1 = [] ),
( B > C -> X2 = [2|X1] ; X2 = X1 ),
( C > D -> X3 = [3|X2] ; X3 = X2 ),
( X3 = [] -> R = [A,B,C,D]
; member( X, X3),
( X =:= 1 -> dgsortn([B,A,C,D],R)
; X =:= 2 -> dgsortn([A,C,B,D],R)
; X =:= 3 -> dgsortn([A,B,D,C],R) )).
Running it succeeds 5 times:
9 ?- dgsortn( [7,11,5,3], R).
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11].
We could also add a counter to see the number of steps taken to produce each solution.

I directly takled the counting problem and came up with this solution. It has the defect that the negation as failure will re-evaluate the conditions.
sortloop([Q1,Q2,Q3,Q4], R) :- Q1 > Q2, sortloop([Q2,Q1,Q3,Q4], R).
sortloop([Q1,Q2,Q3,Q4], R) :- Q2 > Q3, sortloop([Q1,Q3,Q2,Q4], R).
sortloop([Q1,Q2,Q3,Q4], R) :- Q3 > Q4, sortloop([Q1,Q2,Q4,Q3], R).
sortloop([Q1,Q2,Q3,Q4], [Q1,Q2,Q3,Q4]) :- \+ Q1 > Q2, \+ Q2 > Q3, \+ Q3 > Q4.
But it shows there are 5 execution paths:
?- sortloop([7,11,5,3], R).
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
false.
But can we improve that the conditions are only evaluated once? What came to my mind was the soft cut (*->)/2 since the action lists do not abort:
sortloop2([Q1,Q2,Q3,Q4], R) :-
((Q1 > Q2, sortloop2([Q2,Q1,Q3,Q4], R);
Q2 > Q3, sortloop2([Q1,Q3,Q2,Q4], R);
Q3 > Q4, sortloop2([Q1,Q2,Q4,Q3], R)) *-> true; R=[Q1,Q2,Q3,Q4]).
The soft cut solution gives the same result:
?- sortloop2([7,11,5,3], R).
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
false.
But none of the solutions uses a Prolog cut that would replace the arrow between condition and action list in the Dijkstra guards. Situation is a little bit more complicated.
Edit 12.02.2021:
In case somebody is currious how the 5 paths look like as a DAG:

Related

Making prolog predicates deterministic

I've written a predicate, shuffle/3, which generates "shuffles" of two lists. When the second and third argument are instantiated, the first argument becomes a list which has all the elements of both Left and Right, in the same order that they appear in Left and Right.
For example:
?- shuffle(X, [1, 2], [3, 4]).
X = [1, 3, 2, 4] ;
X = [1, 3, 4, 2] ;
X = [1, 2, 3, 4] ;
X = [3, 4, 1, 2] ;
X = [3, 1, 2, 4] ;
X = [3, 1, 4, 2] ;
false.
Here's the code I've come up with to implement it:
shuffle([], [], []).
shuffle([H|R], [H|Left], Right) :- shuffle(R, Right, Left).
shuffle([H|R], Left, [H|Right]) :- shuffle(R, Right, Left).
This works well, and even generates reasonable results for "the most general query", but it fails to be deterministic for any query, even one where all arguments are fully instantiated: shuffle([1, 2, 3, 4], [1, 2], [3, 4]).
My real question is: is there anything I can do, while maintaining purity (so, no cuts), which makes this predicate deterministic when all arguments are fully instantiated?
And while I'm here, I'm new to Prolog, I wonder if anyone has advice on why I would care about determinism. Is it important for real prolog programs?
No, there is no way to make this predicate deterministic while still maintaining pure code. To see this, consider:
?- shuffle([1, 1], [1], [1]).
true
; true.
There are two answers to this. Why? The best is not to use a debugger to understand this, but rather to use a generalized query:
?- shuffle([X1, X2], [Y1], [Y2]).
X1 = Y1, X2 = Y2
; X1 = Y2, X2 = Y1.
So here you can see the "true" connection between the arguments! And now our specific query is an instance of this more general query. Thus, no way to remove the two answers.
However, you might use cut in a pure way, provided it is guarded such that the result will always be pure. Like testing ground(shuffe(Xs, Ys, Zs)) but all of this is quite ad hoc.
On second thought, there might be a pure, determinate answer, but only if the answers to shuffle([X1, X2], [Y1], [Y2]). are changed somehow. The answer actually should be:
?- shuffledet([X1, X2], [Y1], [Y2]).
X1 = X2, X2 = Y1, Y1 = Y2 % all equal
; dif(X1, X2), X1 = Y1, X2 = Y2
; dif(X1, X2), X1 = Y2, X2 = Y1.
So that might be a possibility... I will had put a 500 bounty on this ASAP, but no response. And again I try another one.
The way to make the more det version of shuffle is using if_/3 from library module reif:
shuffle_det1( A,B,C):-
if_( B=[], A=C,
if_( C=[], A=B,
( B=[BH|BT], C=[CH|CT], A=[AH|AT], (
AH=BH, shuffle_det1( AT, BT, C)
;
AH=CH, shuffle_det1( AT, B, CT) ) ))).
Working positionally, it's OK, and indeed eliminates some (most?) spurious choice points:
40 ?- shuffle_det1(X, [1, 2], [3, 4]).
X = [1, 2, 3, 4] ;
X = [1, 3, 2, 4] ;
X = [1, 3, 4, 2] ;
X = [3, 1, 2, 4] ;
X = [3, 1, 4, 2] ;
X = [3, 4, 1, 2].
41 ?- shuffle_det1(X, [11,12], [11,22]).
X = [11, 12, 11, 22] ;
X = [11, 11, 12, 22] ;
X = [11, 11, 22, 12] ;
X = [11, 11, 12, 22] ;
X = [11, 11, 22, 12] ;
X = [11, 22, 11, 12].
81 ?- shuffle_det1([1,2,3,4], [3, 4], [1, 2]).
true.
But:
82 ?- shuffle_det1([1,2,3,4], [1, 2], [3, 4]).
true ;
false.
Also, as [user:false] points out, if two lists' head elements are equal, there's some redundancy in the answers:
11 12 13 .. B
21 22 23 .. C
11 (12.. + 21..) | 21 (11.. + 22..)
12 (13.. + 21..) 11 (12.. + 22..) *
| 21 (12.. + 22..) * | 22 (11.. + 23..)
Here the two cases marked with * actually conflate when 11 == 21. To combat that, we "unroll" the picking by doing two in a row in such cases:
shuffle_det( A,B,C):-
if_( B=[], A=C,
if_( C=[], A=B,
( B=[BH|BT], C=[CH|CT], A=[AH|AT],
if_( \X^(dif(BH,CH),X=true ; BH=CH,X=false),
(
AH=BH, shuffle_det( AT, BT, C)
;
AH=CH, shuffle_det( AT, B, CT) ),
(
AH=BH, AT=[CH|A2], shuffle_det( A2, BT, CT) % **
;
pull_twice( A,B,C)
;
pull_twice( A,C,B)
))))).
pull_twice([BH|AT],[BH|BT],C):- % B,C guaranteed to be non-empty
if_( BT=[], AT=C,
( BT=[BH2|B2], AT=[BH2|A2], shuffle_det(A2,B2,C) )).
Testing:
35 ?- shuffle_det(A, [11,12], [11,22]).
A = [11, 11, 12, 22] ;
A = [11, 11, 22, 12] ;
A = [11, 12, 11, 22] ;
A = [11, 22, 11, 12].
This is already much better than shuffle_det1. But it's not fully right yet:
38 ?- shuffle_det(A, [1], [1]).
A = [1, 1] ;
A = [1, 1] ;
A = [1, 1].
The two pull_twice calls are probably the culprit. Somehow there must be only one, which would decide whether to do the other one or not...

Write a program in prolog that determines if there are exactly three values in such a way that they add upto a sum of N

Eg:
List[1,2,3,4,5,6] with N equal to 6 should print true because there are exactly 3 values that add upto 6. 1+2+3.
List[2,5,7,9] with N equal to 12 should print false as there are no 3 elements that add upto 12.
Let's maybe start with a more general predicate that describes the relation between a list, a sublist of said list and the sum of numbers in the sublist. To make it obvious which argument is what, it is opportune to chose a descriptive name for the predicate, say sum_ofsub_fromlist/3. Now let's observe that if the first argument is the sum of the numbers in the sublist, then successively subtracting those numbers from the sum yields zero, e.g.: X=A+B → X-A-B=0. So there will be a base case that contains 0 as the sum and [] as the sublist (rule 1) and a recursive rule that subtracts the elements of the sublist from the sum (rule 2). And since a sublist does not contain all elements of the list it's taken from in general, there will be a recursive rule for skipping elements of the list that do not occur in the sublist (rule 3). This rule is only needed as long as there are still elements in the sublist, so a constraint would be beneficial, that prevents this rule from succeeding once the sublist is empty. These ideas can be realized in Prolog like so:
sum_ofsub_fromlist(0,[],_L). % rule 1
sum_ofsub_fromlist(X,[A|Bs],[A|As]) :- % rule 2
X0 is X-A,
sum_ofsub_fromlist(X0,Bs,As).
sum_ofsub_fromlist(X,Bs,[_A|As]) :- % rule 3
dif(Bs,[]), % constraint: sublist not empty
sum_ofsub_fromlist(X,Bs,As).
You can query this predicate to assure yourself that it delivers all sublists for the given sum in your examples:
?- sum_ofsub_fromlist(6,S,[1,2,3,4,5,6]).
S = [1, 2, 3] ;
S = [1, 5] ;
S = [2, 4] ;
S = [6] ;
false.
?- sum_ofsub_fromlist(12,S,[2,5,7,9]).
S = [5, 7] ;
false.
Building on this you can then write a calling predicate that only succeeds for sublists of length three:
sum_oftriple_fromlist(S,T,L) :-
T=[_,_,_], % T has to be a triple
sum_ofsub_fromlist(S,T,L).
This predicate yields the answers you desire:
?- sum_oftriple_fromlist(6,T,[1,2,3,4,5,6]).
T = [1, 2, 3] ;
false.
?- sum_oftriple_fromlist(12,T,[2,5,7,9]).
false.
Note that the predicate is also working with negative numbers:
?- sum_oftriple_fromlist(6,T,[-5,-3,-1,2,4,7,8,9]).
T = [-5, 2, 9] ;
T = [-5, 4, 7] ;
T = [-3, 2, 7] ;
false.
?- sum_oftriple_fromlist(-6,T,[-6,-5,-4,-3,-2,-1,2,4]).
T = [-6, -4, 4] ;
T = [-6, -2, 2] ;
T = [-5, -3, 2] ;
T = [-3, -2, -1] ;
false.
However, due to the use of is/2, the predicate only works if the first and the third arguments are ground:
?- sum_oftriple_fromlist(S,T,[1,2,3,4,5,6]).
ERROR: is/2: Arguments are not sufficiently instantiated
Exception: (7) sum_ofsub_fromlist(_G918, [_G1016, _G1019, _G1022], [1, 2, 3, 4, 5, 6]) ?
?- sum_oftriple_fromlist(6,T,[A,B,C,D,E,F]).
ERROR: is/2: Arguments are not sufficiently instantiated
Exception: (7) sum_ofsub_fromlist(6, [_G2121, _G2124, _G2127], [_G1945, _G1948, _G1951, _G1954, _G1957, _G1960]) ?
If that's fine with you, you can stop here. Alternatively, you could opt to make the predicate more versatile by using CLP(FD). Just apply these minor changes to your code:
:- use_module(library(clpfd)). % <- new
sum_oftriple_fromlist(S,T,L) :-
T=[_,_,_],
sum_ofsub_fromlist(S,T,L).
sum_ofsub_fromlist(0,[],_L).
sum_ofsub_fromlist(X,[A|Bs],[A|As]) :-
X0 #= X-A, % <- change
sum_ofsub_fromlist(X0,Bs,As).
sum_ofsub_fromlist(X,Bs,[_A|As]) :-
dif(Bs,[]),
sum_ofsub_fromlist(X,Bs,As).
Now the above queries deliver answers:
?- sum_oftriple_fromlist(S,T,[1,2,3,4,5,6]).
S = 6,
T = [1, 2, 3] ;
S = 7,
T = [1, 2, 4] ;
S = 8,
T = [1, 2, 5] ;
. % another
. % seventeen
. % results here
The second query, however, yields residual goals (see documentation for details) as results:
?- sum_oftriple_fromlist(6,T,[A,B,C,D,E,F]).
T = [A, B, C],
_G2424+A#=6,
C+B#=_G2424 ;
T = [A, B, D],
_G2424+A#=6,
D+B#=_G2424 ;
.
.
.
To get actual numbers, you have to restrict the range of the numbers and subsequently label the variables in the list:
?- L=[A,B,C,D,E,F], sum_oftriple_fromlist(6,T,L), L ins 1..6, label(L).
L = [1, 1, 4, 1, 1, 1],
A = B, B = D, D = E, E = F, F = 1,
C = 4,
T = [1, 1, 4] ;
L = [1, 1, 4, 1, 1, 2],
A = B, B = D, D = E, E = 1,
C = 4,
F = 2,
T = [1, 1, 4] ;
.
.
.
Possibly you are only interested in lists where every number only appears once:
?- L=[A,B,C,D,E,F], all_distinct(L), sum_oftriple_fromlist(6,T,L), L ins 1..6, label(L).
L = [1, 2, 3, 4, 5, 6],
A = 1,
B = 2,
C = 3,
D = 4,
E = 5,
F = 6,
T = [1, 2, 3] ;
L = [1, 2, 3, 4, 6, 5],
A = 1,
B = 2,
C = 3,
D = 4,
E = 6,
F = 5,
T = [1, 2, 3] ;
.
.
.
Or maybe you don't even want to restrict the sum:
?- L=[A,B,C,D,E,F], all_distinct(L), sum_oftriple_fromlist(S,T,L), L ins 1..6, label(L).
L = [1, 2, 3, 4, 5, 6],
A = 1,
B = 2,
C = 3,
D = 4,
E = 5,
F = S, S = 6, % sum = 6
T = [1, 2, 3] ;
.
.
.
L = [1, 2, 4, 3, 5, 6],
A = 1,
B = 2,
C = 4,
D = 3,
E = 5,
F = 6,
S = 7, % sum = 7
T = [1, 2, 4] ;
.
.
.
As you can see the CLP(FD) version of the predicate resembles a true relation as opposed to the non-CLP(FD) version. And of course your example queries yield the same answers with both versions.
Your code only considers the first 3 items in the list, and not any other combinations.
The most natural way to structure a solution involving a list is to base your recursion on the structure of the list. So:
If the first element of the list (say, X) is to be included in the 3 values that sum to N, we need to find a way to find 2 values in the rest of the list that sum to N-X.
If it isn't, just try to solve the problem using the rest of the list.
Note that you may need a "helper" version of your predicate that allows you to add other parameters. In this case, knowing how many values you need to add up would be helpful.

Generate a list based on constraint result, using Prolog's CLPFD

I'm attempting to implement a Skyscraper puzzle solver in Prolog, using constraints (CLPFD).
I've realized a great constraint would be calculating the number of times the maximum switches whilst traversing each row and column and matching it with the side clue.
Here's an example of a single row:
*2* [ _ | _ | _ | _ ] *3* -> *2* [ 1 | 4 | 3 | 2 ] *3*
The list [1, 4, 3, 2] works for clue 2 because it has 2 maximum switches (0 -> 1 -> 4).
It also works for clue 3 because the same list reversed - [2, 3, 4, 1] - has 3 maximum switches (0 -> 2 -> 3 -> 4).
I've managed to program a predicate that returns me the number of maximum switches of a list.
The issue is how to actually utilize it to generate a new constraint? I can't pass my list/matrix directly because it isn't yet initialized.
It should probably be something like:
calculate_max_switches(List, Switches),
% Generate a list whose Switches value is equal to the clue number.
Thank you.
Without seeing your code, here is my hint, adapted from my previous answer:
:- use_module(library(clpfd)).
skyscrape_row(Left, Right, Heights) :-
constraint_view(0, Heights, LHeights),
sum(LHeights, #=, Left),
reverse(Heights, Heights_),
constraint_view(0, Heights_, RHeights),
sum(RHeights, #=, Right).
constraint_view(_, [], []).
constraint_view(Top, [V|Vs], [R|Rs]) :-
R #<==> V #> 0 #/\ V #> Top,
Max #= max(Top, V),
constraint_view(Max, Vs, Rs).
that applied to your example yields
?- L=[A,B,C,D], L ins 1..4, all_different(L), skyscrape_row(2,3,L), label(L).
A = 1,
B = 4,
C = 3,
D = 2,
L = [1, 4, 3, 2]
A = 2,
B = 4,
C = 3,
D = 1,
L = [2, 4, 3, 1]
A = 3,
B = 4,
C = 2,
D = 1,
L = [3, 4, 2, 1]
Live code is available in SWISH

Prolog counting with restrictions

I don't know how can I achieve the following:
I want to count the number of times a certain condition (whose values are unknown) is met.
For instance, if I have the lists [A1,A2,A3] and [B1,B2,B3], how can I
create a list [R1,R2,R3] where Ri is 1 if Ai=Bi and 0 if not.
This is the basis of the "program".
:- use_module(library(clpfd)).
main(A,B) :-
length(A,3),
domain(A,1,3),
all_different(A),
length(B,3),
domain(B,1,3),
all_different(B),
append(A,B,L),
labeling([],L).
you should 'reify' your conditions, posting constraints of the form
reify(A,B,C) :-
C #<==> A #= B.
between pairs of variables. maplist/3 it's an handy shortcut
:- use_module(library(clpfd)).
% simulate domain/3 in SWI-prolog
domain(Vs,L,H) :- Vs ins L..H.
reify(A,B,C) :-
C #<==> A #= B.
main(A,B,C) :-
length(A,3),
domain(A,1,3),
all_different(A),
length(B,3),
domain(B,1,3),
all_different(B),
maplist(reify, A,B,C),
labeling([],A),
labeling([],B).
yields
1 ?- main(A,B,C).
A = B, B = [1, 2, 3],
C = [1, 1, 1] ;
A = [1, 2, 3],
B = [1, 3, 2],
C = [1, 0, 0] ;
A = [1, 2, 3],
B = [2, 1, 3],
C = [0, 0, 1]
etc ....

Get list of sets where the sum of each set is X

I'm trying to figure out how to generate a list of sets, where each set has a length of N and the sum of each set is X.
I found this code:
num_split(0,[]).
num_split(N, [X | List]):-
between(1,N,X),
plus(X,Y,N),
num_split(Y,List).
And I can use that to get a list of sets with sum X:
num_split(6,List),length(List,5).
List = [1, 1, 1, 1, 2] ;
List = [1, 1, 1, 2, 1] ;
List = [1, 1, 2, 1, 1] ;
List = [1, 2, 1, 1, 1] ;
List = [2, 1, 1, 1, 1] ;
false.
The problem is that those are all permutations, and I'm looking for combinations. The output I'm looking for should be something like get_combos(Sum,Length,List):
get_combos(6,2,List).
List = [5,1];
List = [4,2];
List = [3,3];
false.
Any pointers?
If you have access to a CLP(FD) library, you can use this code:
:- [library(clpfd)].
get_combos(Sum, Length, List) :-
length(List, Length),
List ins 1 .. Sum,
% all_distinct(List), not really useful here
sum(List, #=, Sum),
chain(List, #<),
label(List).
test:
?- get_combos(10,3,L).
L = [1, 2, 7] ;
L = [1, 3, 6] ;
L = [1, 4, 5] ;
L = [2, 3, 5] ;
Maybe I misunderstood your question. Use this chain
...
chain(List, #=<),
....
to get possible duplicates values:
?- get_combos(10,3,L).
L = [1, 1, 8] ;
L = [1, 2, 7] ;
L = [1, 3, 6] ;
L = [1, 4, 5] ;
L = [2, 2, 6] ;
L = [2, 3, 5] ;
L = [2, 4, 4] ;
L = [3, 3, 4] ;
false.
Enforce an "equal or greater" restriction between successive values in the array.
You can add it on as another predicate:
is_combination([]).
is_combination([_]).
is_combination([A,B|List]) :- A =< B, is_combination([B|List]).
get_combos(Sum, Length, List) :-
num_split(Sum, Length, List),
is_combination(List).
Unfortunately, tacking it on the end of the num_split/3 does not necessarily increase its performance, so adding it directly into the algorithm would be marginally better:
get_combos(_, 0, []).
get_combos(Sum, 1, [Sum]).
get_combos(Sum, Length, [A, B|List]) :-
between(1, Sum, A),
plus(A, NextSum, Sum),
plus(1, NextLength, Length),
get_combos(NextSum, NextLength, [B|List]),
A =< B.
I'm not sure just how much more performance this gets, as the comparison has to be after the recursion, due to the less-than-or-equals operator (=<) requiring both operands to be fully instantiated for it to work.

Resources