So welcome to the reference tutorial on Option Types. So what are option types? Well, many models involve decision that are only meaningful if other decisions are made in a particular way. So for example, let's say in a configuration problem, I decided to buy rack-205e, and if that's the case, I need to decide the number of internal slots, which are between two and five. If I buy a different kind of rack I might need to, I may have no internal slots. There's no decision about internal slots. So this decision about the number of individual slots that I need, that only makes sense if I actually choose a configuration with a rack-205e. So what we ought to be able to do is represent these optional decisions so this decision about the number of internal slots is optional. It only makes sense In the case that I make another decision. And in particular we want to make sure that if these optional decisions aren't meaningful, if they don't have any meaning. Then they can't constrain any other part of the problem and that's for what MiniZinc uses option types. And you may have met option types in your error messages, wondering where are these option types coming from. We'll talk about how option types are also used to make MiniZinc more expressive. Here's an example of where we might want to use option types. We've got N workers and two M tasks designed in two rows. One is M and M+1 to two M and we need to assign workers to task to maximize profits, this is just a straight assignment problem as usual. And the only constraint is that workers assigned to two adjacent tasks have to be compatible. So here we have our profit array, our compatibility between pairs of workers, decisions we need to make for each worker, what task does he work on. We obviously constraint that every worker works on a different task and here's our objective which is just summing up the problem. But how do we model the compatibility constraint? So the obvious way to do it is use an implication, if two workers are adjacent then they must be compatible. So basically, we look over all pairs of workers, and we check. If the task that worker 2 works on is one more than the task that worker 1 works on. And the task worker 1 works on is not the nth task because remember task m and task m plus one are not adjacent because they're in different rows. Then we have to ensure the two workers are compatible, so that is perfectly fine, that is a great model for the problem. But it does generate a large number of very weak constraints. Basically we have a number for every pair of workers we have one called constraints, and to make sure that the compatibility constraint holds. So what we'd rather do, is basically invert the task function. If we had a worker function which told us for each task what worker works on it, then it'd be very easy to write down that compatibility constraint. But the problem is it's not a bijection so we can't inverse the task function. The inverse is actually a partial function. So we need to map each task to a worker or to the possibility that there's no worker working on that task because we don't have a projection. And what option types do is add this extra value which I'll call opt, which is the less than, greater than sign to a type. And now that's going to allow us to invert the task functions, so we're going to have a decision. So for each task, which is the worker that works on the task or it might be this optional value, which meaning no worker works on that task. And then, we can invert this task array to get this worker right. Note, this is not exactly the same inverse we've seen before, which only worked for bijections. Now, with the option values we need to make sure that either the task worked on, by worker w is the worker who works on the same task, or there's no such worker. Right? this is a new work constraint. So if we had that, we can model compatibility much more directly. So now we can just say, for every task in 1 to 2 times n- 1, except for the one where t is n. The worker who works on that task and the worker who works on the task which is one further along have to be comparable all right? And the important point about how this structure work is if one of these workers doesn't exist they opt. Then we want to let the constraint hold because that's saying this is a meaningless variable. Worker on task T is a meaningless variable, doesn't make any sense, so we don't want to cause failure. We can't constrain something which doesn't exist. So option types basically extend the base types by adding this extra value opt, so you've got optional integers and floats and bools. And the way we can think of them is optional type variables act like the normal variable if they take a value different from opt. And they act as if they weren't part of a constraint if they take the value opt, so an example of that is we look at this. All different, well, it's alldifferent constraint is clearly false, because as 2 values which take the value so that it's same value. But that's not how we want to consider it, we want to consider that this constraint holds because if we ignore the opt values, we'd see all different. 3, 6, 1, 8, 7, 5, and that would actually hold, so we want to think about these opt values as not actually existing. And we have to be careful about how they act in expressions, rather than just constraints? So when I have opt + 3, I want to consider that the same as 3 because 0 is that left identity for plus an right identity, so opt acts like this left identity it acts like 0, so we stipulate it to three. Similarly, 2 minus opt is going to give me 2 because 0 is a right identity for minus. And opt times opt is going to be 1 because 1 is a left and right identity for multiplication.
So if there's no identity for that position, then the, the opt value just propagates upwards. So opt minus two gives you opt because there's no left identity for a subtraction and opt divided by four gives you opt, similarly. If you end up option types in user-defined groups and functions, then you are going to need to define their behavior. So, why it's important to learn about option types, because you're already using them. So, I'm going to show you where they're hidden and how you can avoid them. So, any time you iterate over variable sets, you're actually implicitly using option types. So this expression here, the sum(i in 1..x) so this x is a variable set. Is actually syntactic sugar for this. It says we're going to iterate all the possible ways this variable set could take. All the fixed possible ways, right? And if i is in x, then we're going to add up the size. Otherwise, we're going to get returned by the value opt. So we're going to add the size, or we're going to add opt. And of course adding opt acts like zero so this ends up just summing up the sizes for the things which are in set x. Similarly, the same thing happens if I have a variable where clause. So here the where clause, since x is a variable, I don't know if this holds or not. In fact, this is exactly the same expression as before just writen in a different way and indeed syntactic sugar is identical to what we saw before. All right, we want to sum up only the cases where this constraint holds, so if it holds then we get size y otherwise we get this opt value. So you can avoid option types by avoiding iterating over variable sets. So just think about whenever you're iterating over a variable set we're going to replace. So typically, rather than iterate out of this variable set, we're going to find its upper bound over that, so it's going to go over all the possibly ways it could be in x. We only want to add the ones where i is in x. So this expression here will evaluate to one if i is in x, and zero if it's not, which means the size will be cancelled out won't be summed up. Similarly if we have a forall, so forall i in x we want this constraint to hold. Well, actually it's the same, for any i which could be an x if i is an x we want the constraint to hold. So this implication will do the same thing and similarly exists i in x where this constraint holds. We want to find sum i in the upper bound of x so this is a fixed set. We have both i is actually an x and this constraint holds. The same thing happens when using variable where clauses, so here, we're summing up all the sizes where i is greater than x. We can just use this book2int expression to calculate the ones where i is greater than x and then we have this i where it's not. Then we're going to get zero so the sum won't include. And similarly for the forall we just make this i greater to x in here and in the implication which is forced the constraint to hold only in i is greater than x. And it similarly, here exists we want for when i is greater than x so x is a variable again. We want to find an x in the set where i is greater than or equal to x and the size of i is less or equal to cap which is exactly the same statement with no optional types. So let's look at some more complicated cases. So we can do the same for max and min but it's a bit more complicated. So here we're trying to calculate a minimum for sum i in a variable set x of the size of i and the sizes themselves may be variables. So what we need to do to build this is, first of all, find some value which is larger than any possible legitimate value that this expression can take. So we can do that by saying, well let's take the maximum of any i which could be in the upper bound of x, of the upper bound of that expression, and then we'll add 1. So this number is larger than any possible value that could be in this set here. So we know that n will always take a value less than that if it takes a value at all. Once we've done that, we iterate over all values in the upper bound of x so that's a fixed set. If i is in x then we get its size. Otherwise, we get this larger value and that means that the min will never pick this larger value. So if there is some legitimate value for size, some value i in x and this size of i will be smaller and it'll be the min chosen for m. So this would work all the time except for the one case where actually x is empty. In which case the min actually fails and where as this will return m with a value of larger. But you can test for that in in your if m takes a value which is larger, which is equal to larger or greater than, is clearly not a correct min, and you should maybe do the same thing as this fail. But normally, when we're taking these min expression we dont expect them to fail. Another more complicated case is for this we're doing an alldifferent(y(i)) for the i is in this variable set x. So we can just have an all different build on option types in our library, it's not likely to be native. Most constraint folders won't be handling that, but we can avoid hidden option types by written code another way. So here, we're encoding exactly the same thing, except been using this bool2int expression here to convert all the y in i values into 0s, all right? Now since all the legitimate value for y take values that are 1 to m, we know that any 0 value which we get in this list actually doesn't represent anything, it's optional an value. It's a value where i was not in x and so we can use alldifferent_except_0 to get the same effect, right? All the y variations they won't be 0. They have to all be different and for all the values where i is not in x will fo to 0 and then that's fine because alldifferent_except_0. So in summarizing, there are many problems that involve decisions that only make sense of are first and option types provide a concise way of expressing that. And the reason why you might definitely want to learn about them, at least to avoid them, is that whenever you write an equation for a variable set or that includes have a variable where clause. And option types are used and normally, it will silently perform as intended. But if you're getting confused by error messages, then we suggest that you replace those iterations to avoid option types all together. That brings us to the end of the section.