Assignment for university

Yes, clearly constraint solving is what this problem is about and Prolog constraint solver has the machinery to do just that.

It seems, however, that the course instructor wanted a Prolog solution – or so, it seems to me.

Although, I would be interested in seeing how this can be solved with constraint solver – in particular given that there are N buckets – and not a fixed number of buckets – that must be allocated.

The answer by Isabelle Newbie I have linked before gives a clue about this.

You’re true that probably a Prolog solution it’s better, but to make it efficient can be difficult. Exactly yesterday I noticed for the first time - after installation of SWISH on a new machine - that among the example notebooks there are 2 solutions of N-Queens, that showcase how the same problem can be solved in plain Prolog or by library(clpfd). Well, ROK’ code maybe cannot be tagged ‘plain’, indeed it’s not so easy to understand where in the snippet the constraints are checked, despite the very clear and documental style.

Do you know any good guide that can help me with installing the libary? Or a list of tutorials?^^

you simply include it in your file.

:- use_module(library(clpfd)).

Have a look at Markus Triska’s excellent material:

https://www.metalevel.at/
https://www.metalevel.at/prolog

I hope you’re telling your fellow students about this conversation, so that they can all benefit. :wink:

For your constraint problem, I suggest focusing on a simple solution and not worrying about how efficient it is. Basically, what you want to do is:
generatecrew(..., Solution), validcrew(Solution).

It might be that generatecrew(..., Solution) can be implemented using permutation/2, you would then write some constraints that verify the potential solution.

If you were to use library(clfpd), you’d do something similar, although it’s possible that CLFPD would allow you to write it in a slightly more succinct fashion. For small problems (such as this), there’s no need to learn about CLFPD.

BTW, the “zebra puzzle” is an example of having a number of constraints; the program generates permutations and tests each permutation against the constraints; so it’s somewhat similar to your airplane problem. Here’s one solution: https://swish.swi-prolog.org/example/houses_puzzle.pl

There are many other ways of doing this, you can find them by doing an Internet search for [zebra prolog].

I haven’t found so far a ‘list of tutorials’ for CLP(FD).

Constraint programming it’s a rather large theme, with fuzzy boundaries, and the details are often complex, since constraint solvers are hosted in languages that require themselves a lot of study.

The N-Queens examples I was speaking above are https://swish.swi-prolog.org/example/queens.pl and https://swish.swi-prolog.org/example/clpfd_queens.pl. N-Queens problem is far easier than scheduling, so easy that allows to compare details of the 2 approaches.

Practically, maybe you should follow @peter.ludemann suggestion: if the problem is small enough to be approached by brute force (permute+filter), go for it. At least, it will give you a way to validate any better encoding you eventually find after examination of library(clpfd)…

I have a kinda weird question I guess.

The predicate we have to write should make use of flight registration number (short: fln)

But because of the #CabinCrew the amount of personal differs. ( N can be 4 or 5)

So my question is how do I use permutation in the correct way so I can permutated lists with different lenghts?

i am not sure if i fully understand the question, but perhaps its useful for you to consider representing list items you permute with pairs, to keep type and number of crew info together during a permute: here is an example:

?- X = [1-a, 2-b, 3-c], permutation(X, Y).
X = Y, Y = [1-a, 2-b, 3-c] ;
X = [1-a, 2-b, 3-c],
Y = [1-a, 3-c, 2-b] ;
X = [1-a, 2-b, 3-c],
Y = [2-b, 1-a, 3-c] ;
X = [1-a, 2-b, 3-c],
Y = [2-b, 3-c, 1-a] ;
X = [1-a, 2-b, 3-c],
Y = [3-c, 1-a, 2-b] ;
X = [1-a, 2-b, 3-c],
Y = [3-c, 2-b, 1-a] ;
false.

A pair is simply a compound term, but its being used by a number of libraries to advantage.

?-
| 1-a = -(1,a).
true.

findall/3 can help you construct lists with pair item (here a “triple”), such as so:

findall(X-Y-Z, flotte(X, _, Y, Z), Ls).

Note that a-b-c, is a nested compound term:

?- a-b-c = -(-(a,b), c).
true.

btw, here is some drafting i thought about tonight – if the problem were fixed to two aircrafts only.

This is only the portion that generates without yet, testing the constraint – it uses append, instead of permutation, to generate alternative crew allocations across two aircraft types.

I wonder how append could be generalized into N lists.

group_type(Type, Group) :-
	findall(P, employee(P, _, Type, _), Group).

alloc_2(P1-S1-Pr1, P2-S2-Pr2) :-
	group_type(pilot, Pilots),
	group_type(steward, Stewards),
	group_type(pursuer, Pursuers),
	append(P1, P2, Pilots),
	append(S1, S2, Stewards),
	append(Pr1, Pr2, Pursuers).		

To clarify my problem:

We have two aircrafts. One of them has in total a need of four crew members, one of them has a need of five.

If I use the flight registration number to get access to the cabincrew
I would have to tell the predicate that it needs four or five arguments
like the X shoudl be:
X = [1-a,2-b,3-c,4-d]
or
X = [1-a,2-b,3-c,4-d,5-e]

and the X is later to be permutated as you have mentioned.
My current thinking is something like X = [ N ], permutation (N,Y).
But here N would be 4 or 5, so it won’t work I guess.

Its always possible to create an auxiliary predicate whose purpose is to do the case analysis and return a result to a caller, whereby the caller only gets a list returned of, say, 4 or 5, but for its further processing only cares that its a list and not about length.

E.g.

aux_predicate(X, Ls) :-
     X =4, 
   process_4(X, Ls).    % returns Ls that is four

aux_predicate(X, Ls) :-
     X =5, 
   process_5(X, Ls).    % returns Ls that is five


predicate(X) :-
              aux_predicate(X, Ls),   % takes care of case analysis
              process(X, Ls).

Edit:

I think i now understand what you are saying – you need to have a constraint that is parameterized by the aircraft type that is then used to determine the needed number of crew members per aircraft type.

I think this also can be done via an auxiliary predciate for testing – the case analysis can be consolidated into a parameter – the aircraft type

% UNTESTED CODE

group_type(Type, Group) :-
	findall(P, employee(P, _, Type, _), Group).

% note how append is used in reverse to split an "appended" list into component lists 
alloc_2(P1-S1-Pr1, P2-S2-Pr2) :-
	group_type(pilot, Pilots),
	group_type(steward, Stewards),
	group_type(pursuer, Pursuers),
	append(P1, P2, Pilots),
	append(S1, S2, Stewards),
	append(Pr1, Pr2, Pursuers).

% constraints cabin crew size which is defined as number of pilots and steward/esses.
test_cabin_crew_size(AircraftType, P, S) :-
     flotte(_,_, AircraftType, CabinCrewSize),
    length(P, P_N),
    length(S, S_N),
    Total is P_N + S_N,
    CabinCrewSize = Total.

     
generate_and_test :-                                                           
    alloc_2(P1-S1-Pr1, P2-S2-Pr2),                                % generate allocation
    test_cabin_crew_size(a300_300, P1, S1) ,               % only accept fitting crew size for aircraft type
    test_cabin_crew_size(crj_900, P2, S2) ,                  % ditto
    ...

The nice thing above is, again, that you simply specify the generator, and then how each generated example is tested, and backtracking ensures you go through all generated examples and get them tested.

This is, again, however, an example where the number of aircraft types is fixed in the code to example 2, and those two in the table – you need to find a way to generalize that to any number of aircraft types, and any kind of routing planning and aircraft allocation per day.

Can you explain me what P1-S1-Pr1 and P2-S2-Pr2 are? (is it a special way to write arguments?

Can you give me an explame for an input for P1-S1-Pr1?

The dash in Prolog is called the pair operator its simply a convenient way to represent a term that relates items to each other

So, instead of writing P1-S1-Pr1 you can write the term aircraft1(P1, S2, Pr1), so long you are consistent between the caller and the predicate.

P1-S1-Pr1 stands for the term: -(P1, -(S1, Pr1))

Dan

See my earlier comment on what pairs are

Edit:

A sample run:

?- alloc_2(P1-S1-Pr1, P2-S2-Pr2).
P1 = S1, S1 = Pr1, Pr1 = [],
P2 = [1, 2, 3, 4, 5, 6, 7, 8],
S2 = [9, 10, 14, 15, 16, 17, 18],
Pr2 = [11, 12, 13] ;
P1 = S1, S1 = [],
Pr1 = [11],
P2 = [1, 2, 3, 4, 5, 6, 7, 8],
S2 = [9, 10, 14, 15, 16, 17, 18],
Pr2 = [12, 13] ;
P1 = S1, S1 = [],
Pr1 = [11, 12],
P2 = [1, 2, 3, 4, 5, 6, 7, 8],
S2 = [9, 10, 14, 15, 16, 17, 18],
Pr2 = [13] .

In general, prefer something like aircraft(P1, S1, Pr1) to P1-S1-Pr1:

  • it’s clearer
  • it’s slightly more efficient (one term instead of 2).

You should reserve X-Y for pairs, typically key-value. See library(pairs).

Note that SWI-Prolog has an extension, called “dicts”, that allows naming fields, e.g. aircraft{pilot:P1,steward:S1,purser:Pr1}. If you prefer not using this extension, I suggest something like: pilot_steward_purser(P1,S1,Pr1) even though a bit long.

(I think Richard O’Keefe mentions this in his book)

I started with P1-S1 and then noticed, I need another one, so i added it :slight_smile:

This is a poor use of “pairs”; the preferred usage is when you have a key-value pair (which also fits with things like library(assoc) and keysort/2).

It’s worth spending a bit of effort to make your data structures clear:
“Show me your [algorithm] and conceal your [data], and I shall continue to be mystified. Show me your [data], and I won’t usually need your [algorithm]; it’ll be obvious.” – Fred Brooks, The Mythical Man Month (1975)

Btw, the way i visualized it in my mind where three buckets, one beside another – but, that’s in my mind – and clearly not clear to anyone else – I should indeed be more careful with that, in my code and in postings.

So did I – it’s just a choice of representation.

P1-S1-Pr1 is nice and compact, and infix notation is nice. But it’s not informative … I had to look at the code to understand what was meant. That’s why something like pilot_steward_purser(P1,S1,Pr1), or even pi_st_pr(P1,S1,Pr1) is better. (I’m not sure about the radical abbreviation; but it’s better than the dashes, which tells you nothing.)

I think Brooks said representation, not data – i will look it up later – if i can find his book again, somewhere in my shelves.

If you’re going to be pedantic, Brooks wrote “flowcharts” and “tables” (according to Fred Brooks - Wikiquote). I just modernized the quote a bit (and indicated that it wasn’t a precise quote by using [algorithm] and [data]). :sunglasses:

“Representation” is a better word, especially in Prolog, where the representation often is the algorithm.