It is possible to write such a higher-order query (e.g., find me a set of items such that no other set of items has a lower total price), but it is not possible to automatically solve it. There are a few workarounds though.
First, here's how you can rewrite your model so that you don't have to manually write 10 different sigs for prices from 1 to 10:
sig Item {
price: one Int
}
pred nItems[n: Int] {
#Item = n
all i: Int | (1 <= i && i <= n) => one price.i
}
fact { nItems[10] }
Now, you could express the aforementioned query in Alloy like this:
fun isum[iset: set Item]: Int {
sum item: iset | item.price
}
run {
some miniset: set Item |
#miniset = 3 and
no iset: set Item |
#iset = #miniset and
isum[iset] < isum[miniset]
} for 10 but 5 Int
but if you try to run it you'll get the following error:
Analysis cannot be performed since it requires higher-order quantification that could not be skolemized.
What you can do instead is to check if there exists a set of items whose total price is lower than a given price, e.g.,
pred lowerThan[iset: set Item, num: Int, min: Int] {
#iset = num and
isum[iset] < min
}
check {
no iset: set Item |
iset.lowerThan[3, 7]
} for 10 but 5 Int
In this example, the property to be checked is there is no set of exactly 3 items whose total price is less than 7. If you now ask Alloy to check this property, you'll get a counterexample in which iset
contains exactly the 3 lowest-priced items, and therefore its total price is 6. Then if you change the check command above to ask if there is a set of 3 items whose total price is lower than 6, you won't get a counterexample, meaning that 6 is indeed the lowest price possible. As you can see, you can't ask Alloy to tell you that the answer is 6 in a single run, but you can run Alloy iteratively to arrive at the same conclusion.