Reindeer Replacements: Solving a children's logic puzzle with OR-Tools

William John Holden

2022-12-18

My daughter brought home this problem from school:

Oh no! Santa's reindeer have come down with the flu, and he has to find some last-minute replacements to pull his sleigh on Christmas Eve. These replacements include dogs, horses, and camels. In order to pull Santa's sleigh, they need to be lined up in the correct order. Use the clues below to help Santa get his reindeer replacements in the correct order.

Reindeer Replacements

This is a constraint satisfaction problem, and it is a nice opportunity to model a simple problem using sophisticated software. I have been interested in constraint solvers for quite a while. They feel magical and can solve very hard problems, yet they're little-known and seldom-used. I recently got started using Google OR-Tools and used it for a small project for an AI class where I tried to schedule CrossFit workouts.

Let's use OR-Tools to solve the Reindeer Replacements problem. Constraint solvers can be pretty difficult to use because it is such a different way of programming, so it really helps to read lots of examples. You have to model your problem in a way that the machine can solve, which might be very different from how you would ordinarily visualize the problem.

Search space

We are trying to match a name (Autumn, Rocket, Spot, ...) to a species (dog, horse, or camel) and position (1-4). I like to visualize this kind of problem as a 3D box of points that represents the Cartesian product of our three axes.

We'll model our problem as $12 \times 3 \times 4=144$ Boolean variables. We will stores these variables in a dictionary named V (for variables). Theoretically, there are $2^{144}$ possible assignments of literal values to these variables -- a seemingly intractable problem. In practice, a modern constraint solver will be able to quickly prune the search space and either find some solution or prove that none exists.

We'll need the OR-Tools library and also itertools.product.

The structure of the problem is that exactly one name goes with each type/order pair.

Equivalently, each type/order pair relates to exactly one name. I think these constraints are actually redundant because there is a 1:1 relationship with the names and type/order pairs, but the machine does not know that. It often helps the solver to supply additional information. This information may seem like an obvious assumption to you, but it might allow the solver to quickly prune subtrees from its search space. Reducing the search space is a good thing when you're coping with exponential complexity.

Clues

Autumn and Rocket are both first in their section.

Rocket isn't a dog.

Autumn isn't a dog or horse.

Reba is first in her section.

Dottie, Blondie, and Jeffrey are all second in their section.

Jeffrey, Lumpy, and Humphrey are camels.

Lumpy is lined up ahead of Humphrey.

This is tricky to model because the constraint solver does not really know about the position numbers. It just sees true/false variables, so we cannot use a simple position(Lumpy) < position(Humphrey) inequality. We can force it by enumerating implications:

position(Lumpy) = 1 ==> position(Humphrey) = 2, 3, or 4
position(Lumpy) = 2 ==> position(Humphrey) = 3 or 4
position(Lumpy) = 3 ==> position(Humphrey) = 4

Note: there must be a flaw here. I can't get this to work with range(1, 4). I have to instead use range(1, 5). Also, it breaks if I specify AddExactlyOne instead of AddAtLeastOne, but I don't know why.

Chad and Rocket are both the same type of animal.

For this one let's use conditional implication. Let $A$ be the statement "Chad and Rocket are both dogs". If $A$ is true, then we'll enforce a constraint about each of them being dogs. Next, statement $B$ is for horses and statement $C$ for camels.

Here, again, I'm having trouble with AddExactlyOne and have had to use AddAtLeastOne instead.

Scout and Spot are both last in their section.

Dottie, Chloe, and Spot are the same type of animal.

We can use the same implication strategy that we used earlier for Chad and Rocket.

Solution