The main issue with your current approach is that it has already defined what each course requires as facts. This means that none of these facts can be contradicted without making the program unsatisfiable. What we need to solve the problem you have posed is a disjunction between the courses that have the same requirements. If we define our facts like the following:
% fulfills(course, requirement)
fulfills(cs_1,1).
fulfills(cs_2,2).
fulfills(cs_3,3) | fulfills(cs_4,3).
it becomes much easier to define a rule to get us the info we want. From this list of facts, we can see that we cs_1 fulfills requirement 1, cs_2 fulfills requirement 2, and either cs_3 or cs_4 fulfills requirement 3. This means that we cannot have both fulfills(cs_3,3)
and fulfills(cs_4,3)
in our answer set. Instead, we can have only one of these facts since if we had both our set would not be minimal.
Although we could write some rules to pare down the answer sets that this generates or define more relationships, the program is good as-is! It generates the following, which matches your desired results:
Answer: 1
fulfills(cs_1,1) fulfills(cs_2,2) fulfills(cs_3,3)
Answer: 2
fulfills(cs_1,1) fulfills(cs_2,2) fulfills(cs_4,3)
SATISFIABLE
Models : 2
Calls : 1
Time : 0.001s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time : 0.000s
We can then extend it to more courses and requirements:
fulfills(cs_1,1).
fulfills(cs_2,2) | fulfills(cs_3,2).
fulfills(cs_4,3) | fulfills(cs_5,3).
fulfills(cs_6,4) | fulfills(cs_7,4) | fulfills(cs_8,4).
fulfills(cs_9, 5).
which gives us a lot more possibilities:
Answer: 1
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_6,4) fulfills(cs_4,3) fulfills(cs_2,2)
Answer: 2
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_6,4) fulfills(cs_4,3) fulfills(cs_3,2)
Answer: 3
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_6,4) fulfills(cs_5,3) fulfills(cs_2,2)
Answer: 4
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_6,4) fulfills(cs_5,3) fulfills(cs_3,2)
Answer: 5
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_8,4) fulfills(cs_4,3) fulfills(cs_3,2)
Answer: 6
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_8,4) fulfills(cs_5,3) fulfills(cs_3,2)
Answer: 7
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_8,4) fulfills(cs_4,3) fulfills(cs_2,2)
Answer: 8
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_8,4) fulfills(cs_5,3) fulfills(cs_2,2)
Answer: 9
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_7,4) fulfills(cs_4,3) fulfills(cs_3,2)
Answer: 10
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_7,4) fulfills(cs_4,3) fulfills(cs_2,2)
Answer: 11
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_7,4) fulfills(cs_5,3) fulfills(cs_3,2)
Answer: 12
fulfills(cs_1,1) fulfills(cs_9,5) fulfills(cs_7,4) fulfills(cs_5,3) fulfills(cs_2,2)
SATISFIABLE
Models : 12
Calls : 1
Time : 0.002s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time : 0.000s
if we want, we could even drop the second argument to fulfills
since we're not using it for anything yet. The following yields the same results as our first example:
course(cs_1).
course(cs_2).
course(cs_3) | course(cs_4).
Typically, we will start an answer set program by defining all of our possible facts and their disjunctions and then limiting them with rules. For this program we have a rather small starting set, but they can get quite large so complex rules can come in handy. However, for this problem we don't need any!
(Hey Dr. Ricks, this is Isaac from Game Programming/Image Processing! Stumbled upon your question, hopefully my answer is helpful!)