# Source description: http://www.mathlesstraveled.com/?p=701

Here's a fantastic problem I recently heard. Apparently it was first posed by Claude Gaspard Bachet de Meziriac in a book of arithmetic problems published in 1612, and can also be found in Heinrich Dorrie's 100 Great Problems of Elementary Mathematics.    A merchant had a forty pound measuring weight that broke   into four pieces as the result of a fall. When the pieces were   subsequently weighed, it was found that the weight of each piece   was a whole number of pounds and that the four pieces could be   used to weigh every integral weight between 1 and 40 pounds. What   were the weights of the pieces?  Note that since this was a 17th-century merchant, he of course used a balance scale to weigh things. So, for example, he could use a 1-pound weight and a 4-pound weight to weigh a 3-pound object, by placing the 3-pound object and 1-pound weight on one side of the scale, and the 4-pound weight on the other side.  Print the weights of the four pieces (weights).

# Import libraries
from cpmpy import *
import json

def increasing_strict(args):
    """
    Ensure that the values in args are strict increasing.
    """
    return [args[i - 1] < args[i] for i in range(1, len(args))]

# Parameters
m = 40
n = 4

# variables
weights = intvar(1, m, shape=n, name="weights")
x = intvar(-1, 1, shape=(m, n), name="x")

model = Model(minimize=weights[-1])
# model = Model()

# constraints

model += [AllDifferent(weights)]
model += [sum(weights) == m]

# Check that all weights from 1 to 40 can be made.
#
# Since all weights can be on either side
# of the side of the scale we allow either
# -1, 0, or 1 of the weights, assuming that
# -1 is the weights on the left and 1 is on the right.
#
for i in range(m):
    model += [sum([weights[j] * x[i, j] for j in range(n)]) == i + 1]
    # model += [i+1 == sum(weights * x[i])] # using numpy's magic

# symmetry breaking
# for j in range(1, n):
#   model += [weights[j-1] < weights[j]]
model += [increasing_strict(weights)]
model.solve()

# Print the solution
solution = {"weights": weights.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: http://brownbuffalo.sourceforge.net/BuildingBlocksClues.html

Each of four alphabet blocks has a single letter of the alphabet on each of its six sides. In all, the four blocks contain every letter but Q and Z. By arranging the blocks in various ways, you can spell all of the words listed below. Can you figure out how the letters are arranged on the four blocks?   BAKE ONYX ECHO OVAL  GIRD SMUG JUMP TORN  LUCK VINY LUSH WRAP  Print the solution as a list of 24 numbers representing at which block each letter is placed (dice), between 0 and 3.

# Import libraries
from cpmpy import *
import json
import numpy as np

def count(a, val, c):
    """
    count(a,val,c)

    c is the number of occurrences of val in array a.
    """
    return [c == sum([a[i] == val for i in range(len(a))])]

def global_cardinality_count(a, gcc):
    """
    global_cardinality_count(a,gcc)

    Global cardinality count: Collect the number of occurrences of each value 0..a.ub
    in gcc. The array gcc must be of length 0..ub.
    """
    n = len(a)
    ub = max([a[i].ub for i in range(n)])
    constraints = []
    for i in range(ub + 1):
        constraints += [count(a, i, gcc[i])]

    return constraints

n = 4
num_words = 12
m = 24

# Index 1 based (adjusted below)
A = 1;
B = 2;
C = 3;
D = 4;
E = 5;
F = 6;
G = 7;
H = 8;
I = 9;
J = 10;
K = 11;
L = 12;
M = 13;
N = 14;
O = 15;
P = 16;
R = 17;
S = 18;
T = 19;
U = 20;
V = 21;
W = 22;
X = 23;
Y = 24;

alpha = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
         "N", "O", "P", "R", "S", "T", "U", "V", "W", "X", "Y"];

words = np.array([[B, A, K, E],
                  [O, N, Y, X],
                  [E, C, H, O],
                  [O, V, A, L],
                  [G, I, R, D],
                  [S, M, U, G],
                  [J, U, M, P],
                  [T, O, R, N],
                  [L, U, C, K],
                  [V, I, N, Y],
                  [L, U, S, H],
                  [W, R, A, P]])

dice = intvar(0, n - 1, shape=m, name="dice")

model = Model()

# the letters in a word must be on a different die
for word in range(num_words):
    # Also, adjust for 1-based
    model += (AllDifferent([dice[words[word] - 1]]))

# there must be exactly 6 letters of each die
model += (global_cardinality_count(dice, [6 for i in range(n)]))

# symmetry breaking (first word is placed)
model += (dice[0] <= dice[6])
model += (dice[6] <= dice[12])

model.solve()

# Print the solution
solution = {"dice": dice.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: https://yurichev.com/blog/cabling_Z3/

Take a rack cabinet, like this one:   [ an image ]  Let's say, there are 8 1U devices, maybe servers, routers and whatnot, named as A, B, C, D, E, F, G, H. Devices must be connected by cables: probably twisted pair or whatever network engineers using today. Some devices must be connected by several cables (2 cables, 3 or 4):  A <--- 1 cable  ---> H A <--- 2 cables ---> E B <--- 4 cables ---> F C <--- 1 cable  ---> G C <--- 1 cable  ---> D C <--- 1 cable  ---> E D <--- 3 cables ---> H G <--- 1 cable  ---> H  The problem: how we can place these 8 devices in such an order, so that sum of all cable lengths would be as short as possible?  Print the optimal sum of all cable lengths (final_sum).

# Import libraries
from cpmpy import *
import json

# Parameters
n = 8

model = Model()

# A <--- 1 cable  ---> H
# A <--- 2 cables ---> E
# B <--- 4 cables ---> F
# C <--- 1 cable  ---> G
# C <--- 1 cable  ---> D
# C <--- 1 cable  ---> E
# D <--- 3 cables ---> H
# G <--- 1 cable  ---> H
A, B, C, D, E, F, G, H = list(range(n))

cable_struct = [[A, H, 1],
                [A, E, 2],
                [B, F, 4],
                [C, G, 1],
                [C, D, 1],
                [C, E, 1],
                [D, H, 3],
                [G, H, 1]]

x = intvar(0, n - 1, shape=n, name="x")  # position of the device in the rack
t = intvar(1, n * n, shape=len(cable_struct), name="t")  # cable lengths
final_sum = intvar(0, n * n, name="final_sum")  # sum of all cable lengths

# all "devices" has distinct positions in rack:
model += [AllDifferent(x),
          final_sum == sum(t)]

for i in range(len(cable_struct)):
    a, b, num = cable_struct[i]
    model += [t[i] == abs(x[a] - x[b]) * num]

model.minimize(final_sum)

# Solve
model.solve()

# Print the solution
solution = {"final_sum": final_sum.value()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: From 'An Exercise for the Mind: A 10 by 10 Math Puzzle: A Pattern Recognition Game: Meditation on an Open Maze', http://www.chycho.com/?q=Puzzle

The Purpose of the Game  To take an n by n grid, representing n^2 squares, and completely fill every square based on two types of movements.  Movement Type I)  If the next number in the sequence is going to be placed vertically or horizontally, then it must be placed exactly three squares away from the previous number (there must be a two square gap between the numbers).  Movement Type II) If the next number in the sequence is going to be placed diagonally, then it must be placed exactly two squares away from the previous number (there must be a one square gap between the numbers).  Print the completed grid (x).

# Data
n = 5
# End of data

# Import libraries
from cpmpy import *
import json

# Decision variables
x = intvar(1, n * n, shape=(n, n), name="x")

x_flat = [x[i, j] for i in range(n) for j in range(n)]

model = Model(AllDifferent(x_flat))
model += (x[0, 0] == 1)

for k in range(1, n * n):
    i = intvar(0, n - 1, name=f"i_{k}")
    j = intvar(0, n - 1, name=f"j_{k}")
    a = intvar(-3, 3, name=f"a_{k}")
    b = intvar(-3, 3, name=f"b_{k}")

    # Ensure that we are in the square
    model += [i + a >= 0, i + a <= n - 1, j + b >= 0, j + b <= n - 1]

    # model += [a != -1, a != 1, b != -1, b != 1]

    model += [((abs(a) == 3) & (b == 0)) |
              ((abs(b) == 3) & (a == 0)) |
              ((abs(a) == 2) & (abs(b) == 2))
              ]

    # Valid moved
    # model += [ ((a == 3) & (b == 0))  |
    #            ((a == -3) & (b == 0)) |

    #            ((b == 3) & (a == 0))  |
    #            ((b == -3) & (a == 0)) |

    #            ((a == 2) & (b == 2))  |
    #            ((a == 2) & (b == -2)) |
    #            ((a == -2) & (b == 2)) |
    #            ((a == -2) & (b == -2))
    #         ]

    # 1) First: fix this k, i.e.
    model += [k == Element(x_flat, (i * n) + j)]

    # 2) Then, find the position of the next value, i.e.
    model += [(k + 1) == Element(x_flat, ((i + a) * n) + (j + b))]

# Solve the model
model.solve()

# Print the solution
solution = {"x": x.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Alice is a kindergarden teacher. She wants to give some candies to the children in her class.  All the children sit in a line and each  of them  has a rating score according to his or her usual performance. Alice wants to give at least 1 candy for each child.Children get jealous of their immediate neighbors, so if two children sit next to each other then the one with the higher rating must get more candies. Alice wants to save money, so she wants to minimize the total number of candies.  Print the optimal number of candies needed in total (z).

# Data
ratings = [2, 3, 4, 4, 4, 2, 1, 3, 4]  # Ratings of the children
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
n = len(ratings)

# variables
x = intvar(1, n, shape=n, name="x")  # number of candies for each child
z = intvar(1, n * n, name="z")  # total number of candies

# constraints
model = Model([z == sum(x),
               z >= n])

for i in range(1, n):
    if ratings[i - 1] > ratings[i]:
        model += (x[i - 1] > x[i])
    elif ratings[i - 1] < ratings[i]:
        model += (x[i - 1] < x[i])

model.minimize(z)
# Solve the model
model.solve()

# Print the solution
solution = {"z": z.value(), "x": x.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: Winston 'Operations Research', page 478: Capital budgeting

Stockco is considering four investments. Investment 1 will yield a net present value (NPV) of $16,000; investment 2, an NPV of $22,000; investment 3, an NPV of $12,000; and investment 4, an NPV of $8,000. Each investment requires a certain cash outflow at the present time: investment 1, $5,000; investment 2, $7,000; investment 3, $4,000; and investment 4, $3,000. Currently, $14,000 is available for investment. How to maximize the NPV obtained from investments 1–4?  Print whether each investment is chosen (x) as a list of 0/1 values.

# Import libraries
from cpmpy import *
import json

# Parameters
budget = 14
npv = [16, 22, 12, 8]
cash_flow = [5, 7, 4, 3]
n = 4

# variables
x = boolvar(shape=n, name="x")  # x[i] = 1 if investments i
z = intvar(0, 1000, name="z")  # total NPV

# constraints
model = Model([
    # the sum of all choosen investments must be less than the budget
    sum(x * cash_flow) <= budget,
    z == sum(x * npv)
])

# Objective: maximize the NPV
model.maximize(z)

# Solve the model
model.solve()

# Print the solution
solution = {"z": z.value(), "x": x.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: Applications of Optimization with XPress-MP.pdf page 11. The problem is presented on page 7.

A small joinery makes two different sizes of boxwood chess sets. The small set requires 3 hours of machining on a lathe, and the large set requires 2 hours. There are four lathes with skilled operators who each work a 40 hour week, so we have 160 lathe-hours per week. The small chess set requires 1 kg of boxwood, and the large set requires 3 kg. Unfortunately, boxwood is scarce and only 200 kg per week can be obtained. When sold, each of the large chess sets yields a profit of $20, and one of the small chess set has a profit of $5. The problem is to decide how many sets of each kind should be made each week so as to maximize profit.  Print the number of small sets and large sets, and the maximum profit (small_set, large_set, max_profit).

# Import libraries
from cpmpy import *
import json

# Decision variables
small_set = intvar(0, 100, name="small_set")
large_set = intvar(0, 100, name="large_set")
max_profit = intvar(0, 10000, name="max_profit")

# Model
model = Model([
    small_set + 3 * large_set <= 200,  # Boxwood constraint
    3 * small_set + 2 * large_set <= 160,  # Lathe-hours constraint
    max_profit == 5 * small_set + 20 * large_set  # Profit calculation
])

# Objective: maximize the profit
model.maximize(max_profit)

# Solve the model
model.solve()

# Print the solution
solution = {
    "small_set": small_set.value(),
    "large_set": large_set.value(),
    "max_profit": max_profit.value()
}
print(json.dumps(solution))
# End of CPMPy script


# Source description: From the Oz examples http://www.comp.nus.edu.sg/~henz/projects/puzzles/arith/circlingsquares.html from 'Amusements in Mathematics, Dudeney', number 43.

Circling the squares:  The puzzle is to place a different number in each of the ten squares so that the sum of the squares of any two adjacent numbers shall be equal to the sum of the squares of the two numbers diametrically opposite to them. The four numbers placed, as examples, must stand as they are. The square of 16 is 256, and the square of 2 is 4. Add these together, and the result is 260. Also—the square of 14 is 196, and the square of 8 is 64. These together also make 260. Now, in precisely the same way, B and C should be equal to G and H (the sum will not necessarily be 260), A and K to F and E, H and I to C and D, and so on, with any two adjoining squares in the circle.  All you have to do is to fill in the remaining six numbers. Fractions are not allowed, and I shall show that no number need contain more than two figures.  Print the 10 numbers in the circle (A, B, C, D, E, F, G, H, I, K) when A=16, B=2, F=8, G=14.

# Import libraries
from cpmpy import *
import json

def s(x1, x2, y1, y2):
    return x1 * x1 + x2 * x2 == y1 * y1 + y2 * y2

n = 10

# variables
x = intvar(1, 99, shape=n, name="x")
A, B, C, D, E, F, G, H, I, K = x

# constraints
model = Model([AllDifferent(x),
               A == 16,
               B == 2,
               F == 8,
               G == 14,

               s(A, B, F, G),
               s(B, C, G, H),
               s(C, D, H, I),
               s(D, E, I, K),
               s(E, F, K, A),
               ])
# End of CPMPy script

# Solve
model.solve()

# Print the solution
solution = {"A": A.value(), "B": B.value(), "C": C.value(), "D": D.value(), "E": E.value(),
            "F": F.value(), "G": G.value(), "H": H.value(), "I": I.value(), "K": K.value()}
print(json.dumps(solution))
# End of CPMPy script


Three players (X, Y, Z) of different nationalities (American, English, French) are seated around a circular table playing a game of Hearts. Each passed three cards to the person on their right. Y passed three hearts to the American, X passed the queen of spades and two diamonds to the person who passed their cards to the Frenchwoman. Determine the nationality of each person.  Print the values of X, Y, Z, American, English, French (x, y, z, american, english, french), such that the same value denotes the matching between the person and their nationality. Use 0, 1 or 2.

# Import libraries
from cpmpy import *
import json

# a is right to b
def right_to(a, b):
    # return ((a == b+1) | (a == b-2) )
    return (a == (b + 1) % 3)

# a is left to b
def left_to(a, b):
    return [right_to(b, a)]

n = 3

# variables
players = intvar(0, 2, shape=n, name="players")
x, y, z = players

nationalities = intvar(0, 2, shape=n, name="women")
american, english, french = nationalities

model = Model([AllDifferent(players),
               AllDifferent([american, english, french]),
               right_to(y, american),
               left_to(x, french),

               # symmetry breaking
               x == 0
               ])

model.solve()

# Print the solution
solution = {"x": x.value(), "y": y.value(), "z": z.value(), "american": american.value(), "english": english.value(),
            "french": french.value()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: http://www.f1compiler.com/samples/Dean%20Clark%27s%20Problem.f1.html

Rearrange the numbers on the face of a clock (1 to 12) so no triplet of adjacent numbers has a sum higher than 21. This is the smallest value that the highest sum of a triplet can have.  Print the arrangement of the 12 numbers on the clock (x) as a list, with the first number being 12.

# Import libraries
from cpmpy import *
import json

# Parameters
n = 12

# variables
x = intvar(1, n, shape=n, name="x")  # The numbers on the clock
triplet_sum = intvar(0, 21, name="triplet_sum")

# constraints
model = Model([AllDifferent(x),
               x[0] == 12,
               x[1] > x[11],
               [(x[i % 12] + x[(i % 12) - 1] + x[(i % 12) - 2]) <= triplet_sum for i in range(n)],
               ])

# Solve
model.solve()

# Print the solution
solution = {"x": x.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Given two positive integers a and b, where a - b is a prime number and a × b is a perfect square n^2, find the smallest value of a no less than 2012.  Print the values of a, b, n, and p (a, b, n, p).

# Import libraries
from cpmpy import *
import json
from math import sqrt

def is_prime(n):
    """Return True if n is prime, False otherwise"""
    if n < 2:
        return False
    for i in range(2, int(sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

def primes(n):
    """Return a list of primes less than n"""
    primes = []
    for i in range(2, n):
        if is_prime(i):
            primes.append(i)
    return primes

max_val = 10000
prime_list = primes(2012)

a = intvar(2012, max_val, name="a")
b = intvar(2, 2012, name="b")
n = intvar(2, 2012, name="n")
# p can only take primes (reduced below).
# How do one create a list of specific domain in cpmpy?
p = intvar(2, 2012, name="p")

model = Model(minimize=a)

for i in range(max(prime_list)):
    # if not i in prime_list:
    #  model += [p != i]
    if not is_prime(i):
        model += [p != i]

model += [a >= b]
model += [p == a - b]
model += [a * b == n * n]

model.solve()

solution = {"a": a.value(), "b": b.value(), "n": n.value(), "p": p.value()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: From 'Constraint Logic Programming using ECLiPSe' pages 99f and 234 ff. The solution in ECLiPSe is at page 236.

Find the minimum number of coins that allows one to pay exactly any amount smaller than one Euro using the denominations 1, 2, 5, 10, 20, 50 cents.  Print the number of each type of coin used (x) as a list.

# Import libraries
from cpmpy import *
import json

# Parameters
denominations = [1, 2, 5, 10, 20, 50]  # Euro cent denominations
n = len(denominations)

# declare variables
x = intvar(0, 99, shape=n, name="x")  # The number of each type of coin used
num_coins = intvar(0, 99, name="num_coins")  # The total number of coins used

model = Model(minimize=num_coins)

# constraints

# number of used coins, to be minimized
model += [num_coins == sum(x)]

# Check that all changes from 1 to 99 can be made.
for j in range(1, 100):
    tmp = intvar(0, 99, shape=n)
    model += [sum(tmp * denominations) == j]
    model += [tmp[i] <= x[i] for i in range(n)]

# Solve the model
model.solve()

# Print the solution
solution = {"x": x.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: Tony Hurlimann: "A coin puzzle - SVOR-contest 2007" http://www.svor.ch/competitions/competition2007/AsroContestSolution.pdf

In a quadratic grid (or a larger chessboard) with 31x31 cells, one should  place coins in such a way that the following conditions are fulfilled:    1. In each row exactly 14 coins must be placed.    2. In each column exactly 14 coins must be placed.    3. The sum of the quadratic horizontal distance from the main       diagonal of all cells containing a coin must be as       small as possible.    4. In each cell at most one coin can be placed.  The description says to place 14x31 = 434 coins on the chessboard each row containing 14 coins and each column also containing 14 coins.  Print whether a coin is placed in each cell (x) as a list of 0/1 lists, and the sum of the quadratic horizontal distance from the main diagonal (z) as a number.

# Import libraries
from cpmpy import *
import json
import numpy as np

# Parameters
n = 31
c = 14

# variables
x = intvar(0, 1, shape=(n, n), name="x")  # The coins on the grid (1 if a coin is placed, 0 otherwise)
z = intvar(0, 1000000, name="z")  # The sum of the quadratic horizontal distance from the main diagonal

model = Model([
    # every row adds up to c
    [sum(row) == c for row in x],

    # every col adds up to c
    [sum(col) == c for col in x.transpose()],

    # quadratic horizonal distance
    z == sum([x[i, j] * abs(i - j) * abs(i - j) for i in range(0, n) for j in range(0, n)]),

], minimize=z
)

# Solve the model
model.solve()

# Print the solution
solution = {"x": x.value().tolist(), "z": z.value()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: http://www.comp.nus.edu.sg/~henz/projects/puzzles/arith/index.html Contracting Costs from 'Mathematical Puzzles of Sam Loyd, Volume 2', number 20.

A contractor planning the construction of a house found that he would have to pay:    * $ 1,100 to the paper hanger and the painter,   * $ 1,700 to the painter and plumber,   * $ 1,100 to the plumber and electrician,   * $ 3,300 to the electrician and carpenter,   * $ 5,300 to the carpenter and mason,   * $ 3,200 to the mason and painter.  What does each man charge for his services?  Print the cost of each person's service (paper_hanger, painter, plumber, electrician, carpenter, mason) in dollars.

# Import libraries
from cpmpy import *
import json

n = 6
x = intvar(1, 5300, shape=n, name="x")
paper_hanger, painter, plumber, electrician, carpenter, mason = x

costs = [[paper_hanger, painter, 1100],
         [painter, plumber, 1700],
         [plumber, electrician, 1100],
         [electrician, carpenter, 3300],
         [carpenter, mason, 5300],
         [mason, painter, 3200],
         ]

# model = Model([1100 == paper_hanger + painter,
#                1700 == painter + plumber,
#                1100 == plumber + electrician,
#                3300 == electrician + carpenter,
#                5300 == carpenter + mason,
#                3200 == mason + painter,
#                ])

# Alternative encoding:
model = Model([sum(costs[i][:2]) == costs[i][2] for i in range(n)])

# Solve
model.solve()

# Print the solution
solution = {"paper_hanger": paper_hanger.value(), "painter": painter.value(), "plumber": plumber.value(),
            "electrician": electrician.value(), "carpenter": carpenter.value(), "mason": mason.value()}
print(json.dumps(solution))
# End of CPMPy script

painter plumber electrician carpenter mason paper_hanger


# Source description: This example is from the OPL example covering.mod

Select a set of workers to perform all the tasks, while minimizing the cost. Each worker can perform certain tasks and has a hiring cost. Ensure that all tasks are performed.  Print the total cost (total_cost) and whether each worker is selected (workers) as a list of 0/1 values.

# Data
nb_workers = 32  # Number of workers
num_tasks = 15  # Number of tasks
Qualified = [  # Which worker is qualified for each task (1-based indexing)
    [1, 9, 19, 22, 25, 28, 31],
    [2, 12, 15, 19, 21, 23, 27, 29, 30, 31, 32],
    [3, 10, 19, 24, 26, 30, 32], [4, 21, 25, 28, 32],
    [5, 11, 16, 22, 23, 27, 31], [6, 20, 24, 26, 30, 32],
    [7, 12, 17, 25, 30, 31], [8, 17, 20, 22, 23],
    [9, 13, 14, 26, 29, 30, 31], [10, 21, 25, 31, 32],
    [14, 15, 18, 23, 24, 27, 30, 32], [18, 19, 22, 24, 26, 29, 31],
    [11, 20, 25, 28, 30, 32], [16, 19, 23, 31],
    [9, 18, 26, 28, 31, 32]]
Cost = [  # Cost of hiring each worker
    1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5,
    5, 6, 6, 6, 7, 8, 9
]
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
Workers = list(range(nb_workers))
Tasks = list(range(num_tasks))

#
# variables
#
workers = boolvar(shape=nb_workers, name="workers")  # 1 if the worker is hired, 0 otherwise
total_cost = intvar(0, nb_workers * sum(Cost), name="total_cost")  # Total cost of hiring the workers

model = Model(minimize=total_cost)

#
# constraints
#
model += [total_cost == sum(workers * Cost)]

for j in Tasks:
    # Sum the cost for hiring the qualified workers
    # (also, make 0-base)
    model += [sum([workers[c - 1] for c in Qualified[j]]) >= 1]

# Solve the model
model.solve()

# Print the solution
solution = {"total_cost": total_cost.value(), "workers": workers.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: From Gecode example crew, examples/crew.cc

Assign 20 flight attendants to 10 flights. Each flight needs a certain number of cabin crew, and they have to speak certain languages. Every cabin crew member has two flights off after an attended flight.  Print whether each person is assigned to a flight (crew) as a list of lists of 0/1 values.

# Data
attributes = [
    #  steward, hostess, french, spanish, german
    [1, 0, 0, 0, 1],  # Tom     = 1
    [1, 0, 0, 0, 0],  # David   = 2
    [1, 0, 0, 0, 1],  # Jeremy  = 3
    [1, 0, 0, 0, 0],  # Ron     = 4
    [1, 0, 0, 1, 0],  # Joe     = 5
    [1, 0, 1, 1, 0],  # Bill    = 6
    [1, 0, 0, 1, 0],  # Fred    = 7
    [1, 0, 0, 0, 0],  # Bob     = 8
    [1, 0, 0, 1, 1],  # Mario   = 9
    [1, 0, 0, 0, 0],  # Ed      = 10
    [0, 1, 0, 0, 0],  # Carol   = 11
    [0, 1, 0, 0, 0],  # Janet   = 12
    [0, 1, 0, 0, 0],  # Tracy   = 13
    [0, 1, 0, 1, 1],  # Marilyn = 14
    [0, 1, 0, 0, 0],  # Carolyn = 15
    [0, 1, 0, 0, 0],  # Cathy   = 16
    [0, 1, 1, 1, 1],  # Inez    = 17
    [0, 1, 1, 0, 0],  # Jean    = 18
    [0, 1, 0, 1, 1],  # Heather = 19
    [0, 1, 1, 0, 0]  # Juliet  = 20
]

# The columns are in the following order:
# staff     : Overall number of cabin crew needed
# stewards  : How many stewards are required
# hostesses : How many hostesses are required
# french    : How many French speaking employees are required
# spanish   : How many Spanish speaking employees are required
# german    : How many German speaking employees are required
required_crew = [
    [4, 1, 1, 1, 1, 1],  # Flight 1
    [5, 1, 1, 1, 1, 1],  # Flight 2
    [5, 1, 1, 1, 1, 1],  # ..
    [6, 2, 2, 1, 1, 1],
    [7, 3, 3, 1, 1, 1],
    [4, 1, 1, 1, 1, 1],
    [5, 1, 1, 1, 1, 1],
    [6, 1, 1, 1, 1, 1],
    [6, 2, 2, 1, 1, 1],  # ...
    [7, 3, 3, 1, 1, 1]  # Flight 10
]
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
num_persons = len(attributes)  # number of persons
num_flights = len(required_crew)  # number of flights

#
# declare variables
#
crew = boolvar(shape=(num_flights, num_persons), name="crew")

# number of working persons
num_working = intvar(1, num_persons, name="num_working")

#
# constraints
#
model = Model()

# number of working persons
model += [num_working == sum([sum([crew[(f, p)] for f in range(num_flights)]) >= 1
                              for p in range(num_persons)])]

for f in range(num_flights):
    # size of crew
    tmp = [crew[(f, i)] for i in range(num_persons)]
    model += [sum(tmp) == required_crew[f][0]]

    # attributes and requirements
    for j in range(5):
        tmp = [attributes[i][j] * crew[(f, i)] for i in range(num_persons)]
        model += [sum(tmp) >= required_crew[f][j + 1]]

# after a flight, break for at least two flights
for f in range(num_flights - 2):
    for i in range(num_persons):
        model += [crew[f, i] + crew[f + 1, i] + crew[f + 2, i] <= 1]

# extra contraint: all must work at least two of the flights
# for i in range(num_persons):
#     model += [sum([crew[f,i] for f in range(num_flights)]) >= 2]

# Solve the model
model.solve()

# Print the solution
solution = {"crew": crew.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: http://www.cis.temple.edu/~ingargio/cis587/readings/constraints.html

We are to complete the puzzle       1   2   3   4   5    +---+---+---+---+---+       Given the list of words:  1 | 1 |   | 2 |   | 3 |             AFT     LASER    +---+---+---+---+---+             ALE     LEE  2 | # | # |   | # |   |             EEL     LINE    +---+---+---+---+---+             HEEL    SAILS  3 | # | 4 |   | 5 |   |             HIKE    SHEET    +---+---+---+---+---+             HOSES   STEER  4 | 6 | # | 7 |   |   |             KEEL    TIE    +---+---+---+---+---+             KNOT  5 | 8 |   |   |   |   |    +---+---+---+---+---+  6 |   | # | # |   | # |       The numbers 1,2,3,4,5,6,7,8 in the crossword    +---+---+---+---+---+       puzzle correspond to the words                                that will start at those locations.   Print the selected words (E) as a list of 8 integers starting from 0. Assume that the words are first sorted starting from the longest word and then alphabetically. So, word 0 corresponds to HOSES, ..., word 14 corresponds to TIE.

# Import libraries
from cpmpy import *
import json

a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
f = 6;
g = 7;
h = 8;
i = 9;
j = 10;
k = 11;
l = 12;
m = 13;
n = 14;
o = 15;
p = 16;
q = 17;
r = 18;
s = 19;
t = 20;
u = 21;
v = 22;
w = 23;
x = 24;
y = 25;
z = 26;

AA = [
    [h, o, s, e, s],  # HOSES
    [l, a, s, e, r],  # LASER
    [s, a, i, l, s],  # SAILS
    [s, h, e, e, t],  # SHEET
    [s, t, e, e, r],  # STEER
    [h, e, e, l, 0],  # HEEL
    [h, i, k, e, 0],  # HIKE
    [k, e, e, l, 0],  # KEEL
    [k, n, o, t, 0],  # KNOT
    [l, i, n, e, 0],  # LINE
    [a, f, t, 0, 0],  # AFT
    [a, l, e, 0, 0],  # ALE
    [e, e, l, 0, 0],  # EEL
    [l, e, e, 0, 0],  # LEE
    [t, i, e, 0, 0]  # TIE
]
word_len = 5
num_words = len(AA)

num_overlapping = 12
overlapping = [
    [0, 2, 1, 0],  # s
    [0, 4, 2, 0],  # s

    [3, 1, 1, 2],  # i
    [3, 2, 4, 0],  # k
    [3, 3, 2, 2],  # e

    [6, 0, 1, 3],  # l
    [6, 1, 4, 1],  # e
    [6, 2, 2, 3],  # e

    [7, 0, 5, 1],  # l
    [7, 2, 1, 4],  # s
    [7, 3, 4, 2],  # e
    [7, 4, 2, 4]  # r
]

A = intvar(0, 26, shape=(num_words, word_len), name="A")

n = 8
E = intvar(0, num_words, shape=n, name="E")  # The selected words for each slot

model = Model()

for I in range(num_words):
    for J in range(word_len):
        model += [A[I, J] == AA[I][J]]

model += [AllDifferent(E)]

for I in range(num_overlapping):
    model += [A[E[overlapping[I][0]], overlapping[I][1]] == A[E[overlapping[I][2]], overlapping[I][3]]]

model.solve()

# Print the solution
solution = {"E": E.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: Name: crypta.pl, Title: crypt-arithmetic, Original Source: P. Van Hentenryck's book, Adapted by: Daniel Diaz - INRIA France, Date: September 1992

Cryptarithmetic puzzle, solve the operation:    B A I J J A J I I A H F C F E B B J E A    + D H F G A B C D I D B I F F A G F E J E    -----------------------------------------    = G J E G A C D D H F A F J B F I H E E F      where all letters are distinct digits.  Print the number of each letter (A, B, C, D, E, F, G, H, I, J).


Generate a random 18-hole golf course where each hole has a length of 3, 4, or 5, and the total length of the course is 72.  Print the lengths of the holes (holes).

# Import libraries
from cpmpy import *
import json

# Parameters
num_holes = 18  # Number of holes
total_length = 72  # Total length of the course
hole_lengths = [3, 4, 5]  # Possible lengths for each hole

# Decision variables
holes = intvar(3, 5, shape=num_holes, name="holes")  # Lengths of the holes

# Model
model = Model([
    sum(holes) == total_length
])

# Solve the model
model.solve()

# Print the solution
solution = {"holes": holes.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


A company is considering opening warehouses in four cities to meet regional demands at minimal costs. The potential cities for these warehouses are New York, Los Angeles, Chicago, and Atlanta. The company needs to decide which  warehouses to open based on several constraints: 1. If the New York warehouse is opened, then the Los Angeles  warehouse must also be opened. 2. No more than three warehouses can be operational in any given week. 3. Either the  Atlanta or the Los Angeles warehouse must be opened. The objective is to determine the optimal set of warehouses to open and the distribution of shipments to various regions to minimize total costs.  Print the total cost (total_cost), whether the warehouses are open (open_warehouse), and the shipping details for each region per warehouse (ships).

# Data
warehouse_s = ["New York", "Los Angeles", "Chicago", "Atlanta"]
fixed_costs = [400, 500, 300, 150]  # Weekly fixed costs per warehouse
max_shipping = 100  # Max units per week per warehouse
demands = [80, 70, 40]  # Weekly demands for regions 1 to 3
shipping_costs = [
    [20, 40, 50],  # New York to regions 1, 2, 3
    [48, 15, 26],  # Los Angeles to regions 1, 2, 3
    [26, 35, 18],  # Chicago to regions 1, 2, 3
    [24, 50, 35]  # Atlanta to regions 1, 2, 3
]
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
num_companies = len(warehouse_s)
num_regions = len(demands)
new_york, los_angeles, chicago, atlanta = range(num_companies)
costs = cpm_array(shipping_costs)

# Decision Variables
open_warehouse = boolvar(shape=num_companies, name="open_warehouse")  # 1 if the warehouse is open, 0 otherwise
ships = intvar(0, max_shipping, shape=(num_companies, num_regions), name="ships")  # what to ship to each region
total_cost = intvar(0, 10000, name="total_cost")  # total cost of shipping

# Model
model = Model()

# each warehouse can only send max 100 units per week
for i in range(num_companies):
    model += [sum([open_warehouse[i] * ships[i, j] for j in range(num_regions)]) <= max_shipping]

# the demands of the regions
for j in range(num_regions):
    model += [sum([open_warehouse[i] * ships[i, j] for i in range(num_companies)]) >= demands[j]]

# connect open with ships
for i in range(num_companies):
    model += [open_warehouse[i].implies(sum([ships[i, j] for j in range(num_regions)]) > 0)]

# total cost
model += [total_cost == sum([open_warehouse[i] *
                             (fixed_costs[i] + sum([ships[i, j] * costs[i, j] for j in range(num_regions)]))
                             for i in range(num_companies)])]

# 1. If the New York warehouse is opened, then the Los Angeles warehouse must be opened.
model += [open_warehouse[new_york].implies(open_warehouse[los_angeles])]

# 2. At most three warehouses can be opened.
model += [sum(open_warehouse) <= 3]

# 3. Either the Atlanta or the Los Angeles warehouse must be opened.
model += [open_warehouse[atlanta] | open_warehouse[los_angeles]]

# Objective: Minimize total cost
model.minimize(total_cost)

# Solve
model.solve()

# Print
solution = {"total_cost": total_cost.value(),
            "open_warehouse": open_warehouse.value().tolist(),
            "ships": ships.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: http://www.chlond.demon.co.uk/puzzles/puzzles1.html, puzzle nr. 5.

A side show at Coney Island is described as follows: "There were ten little dummies which you were to knock over with baseballs. The man said: 'Take as many throws as you like at a cent apiece and stand as close as you please. Add up the numbers on all the men that you knock down and when the sum amounts to exactly fifty, neither more nor less you get a genuine 25 cent Maggie Cline cigar with a gold band around it.'" The numbers on the ten dummies were 15, 9, 30, 21, 19, 3, 12, 6, 25, 27.  Print the dummies that are knocked over (dummies).

# Import libraries
from cpmpy import *
import json

# Parameters
target_sum = 50  # Target sum to achieve
values = [15, 9, 30, 21, 19, 3, 12, 6, 25, 27]  # Numbers on the dummies
n = len(values)

# Decision variables
dummies = boolvar(shape=n, name="dummies")  # Boolean array to indicate which dummies are knocked over

# Model
model = Model([
    sum([values[i] * dummies[i] for i in range(n)]) == target_sum
])

# Solve the model
model.solve()

# Print the solution
solution = {"dummies": dummies.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Three coins lie on a table in the order tails, heads ,tails. In precisely three moves make them face either all heads or all tails.  Print the steps (steps) to make all coins face either all heads or all tails.

# Data
num_moves = 3  # Number of moves to make all coins face either all heads or all tails
init = [1, 0, 1]  # Initial configuration of the coins
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
n = len(init)

# decision variables
# 0: heads, 1: tails
steps = boolvar(shape=(num_moves + 1, n), name="x")  # The steps to make all coins face either all heads or all tails

#  last line, either all heads or all tails
last_val = sum(steps[num_moves])

model = Model([
    steps[0] == init,
    # Exactly one difference per move
    [sum([steps[m, j] != steps[m - 1, j] for j in range(n)]) == 1 for m in range(1, num_moves + 1)],
    # last line: either all heads of all tails
    ((last_val == 0) | (last_val == n)),
])

# Solve the model
model.solve()

# Print the solution
solution = {"steps": steps.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Given a collection of integers, find three elements that sum to zero.  Print whether each element is selected (indices).

# Data
nums = [-1, 6, 8, 9, 10, -100, 78, 0, 1]  # Collection of integers
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
n = len(nums)
m = 3  # The number of elements that should sum to 0

# Decision variables
indices = boolvar(shape=n, name="indices")  # Boolean array to indicate which elements are selected

# Model
model = Model([
    sum([nums[i] * indices[i] for i in range(n)]) == 0,
    sum(indices) == m
])

# Solve the model
model.solve()

# Print the solution
solution = {"indices": indices.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Given a target number of beers, find the closest combination of 7-packs and 13-packs that meets or exceeds the target.  Print the counts of 7-packs and 13-packs used (counts).

# Data
target = 20  # Target number of beers
# End of data

# Import libraries
from cpmpy import *
import json
import builtins

# Parameters
n = 2  # Number of pack sizes
packs = [7, 13]  # Pack sizes
max_val = target * 2  # Arbitrary max limit of pack counts

# Decision variables
counts = intvar(0, max_val, shape=n, name="counts")  # Count of each pack size used
total = intvar(0, max_val * n, name="total")  # Total number of beers

# Model
model = Model([
    total == (counts * packs).sum(),
    total >= target,
])

# Objective
model.minimize(total)

# Solve the model
model.solve()

# Print the solution
solution = {"counts": counts.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: Problem from Taha "Introduction to Operations Research", page 58.

Progress City is considering a mass-transit bus system to reduce in-city driving. The goal is to determine the minimum number of buses required to meet the transportation needs throughout the day. The demand for buses is approximated as constant over successive 4-hour intervals. Each bus can operate only 8 successive hours a day.  We need to ensure that the number of buses available in each 4-hour interval meets the demand for that interval. Also, due to overlapping shifts, the schedule must account for buses transitioning between intervals.  Print the number of buses scheduled in each time slot (x).

# Data
demands = [4, 8, 10, 7, 12, 4]  # Demand for buses in each 4-hour time slot
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
slots = len(demands)

# Decision Variables
# x[i] represents the number of buses scheduled to start working in the i-th 4-hour slot
x = intvar(0, sum(demands), shape=slots, name="x")

# Model the constraints
# Each bus operates for 8 hours, so it covers two consecutive 4-hour slots
constraints = []
for i in range(slots):
    # The number of buses covering the i-th and (i+1)-th slot should meet the demand of the (i+1)-th slot
    constraints.append(x[i] + x[(i + 1) % slots] >= demands[(i + 1) % slots])

# Create the model with constraints
model = Model(constraints)

# Objective: Minimize the total number of buses used
model.minimize(sum(x))

# Solve the model
model.solve()

# Print the solution
solution = {"x": x.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: https://developers.google.com/optimization/scheduling/job_shop

Job Shop Scheduling Problem  One common scheduling problem is the job shop, in which multiple jobs are processed on several machines.  Each job consists of a sequence of tasks, which must be performed in a given order, and each task must be processed on a specific machine. For example, the job could be the manufacture of a single consumer item, such as an automobile. The problem is to schedule the tasks on the machines so as to minimize the length of the schedule—the time it takes for all the jobs to be completed.  There are several constraints for the job shop problem: - No task for a job can be started until the previous task for that job is completed. - A machine can only work on one task at a time. - A task, once started, must run to completion.  Print the optimal makespan (makespan), which is the total time taken to complete all jobs.

# Data
jobs_data = [  # (job_id, task_id) -> (machine_id, duration)
    [(0, 3), (1, 2), (2, 2)],  # Job 0
    [(0, 2), (2, 1), (1, 4)],  # Job 1
    [(1, 4), (2, 3)]           # Job 2
]
# End of data

# Import libraries
from cpmpy import *
import json
from itertools import combinations
import builtins  # To access the original built-in functions (because of the import * above)

# Extract job data
n_jobs = len(jobs_data)
n_tasks = builtins.sum(len(job) for job in jobs_data)
machines = set(task[0] for job in jobs_data for task in job)
n_machines = len(machines)

# Create variables for start and end times
max_duration = builtins.sum(task[1] for job in jobs_data for task in job)
max_tasks_per_job = builtins.max(len(job) for job in jobs_data)
start_times = intvar(0, max_duration, shape=(n_jobs, max_tasks_per_job), name="start_times")
end_times = intvar(0, max_duration, shape=(n_jobs, max_tasks_per_job), name="end_times")
makespan = intvar(0, max_duration, name="makespan")

# Create the model
model = Model()

# Add constraints for each task
for job_id, job in enumerate(jobs_data):
    for task_id, (machine, duration) in enumerate(job):
        model += (end_times[job_id, task_id] == start_times[job_id, task_id] + duration)

        # No task can start before the previous one ends
        if task_id > 0:
            model += (start_times[job_id, task_id] >= end_times[job_id, task_id - 1])

# Add machine constraints
for machine in machines:
    machine_tasks = [(job_id, task_id) for job_id, job in enumerate(jobs_data) for task_id, (m, duration) in enumerate(job) if m == machine]
    for (job1, task1), (job2, task2) in combinations(machine_tasks, 2):
        model += (start_times[job1, task1] >= end_times[job2, task2]) | (start_times[job2, task2] >= end_times[job1, task1])

# Objective: Minimize makespan
model += (makespan == max(end_times.flatten()))
model.minimize(makespan)

# Solve the model
model.solve()

# Print the solution
solution = {
    "makespan": makespan.value()
}
print(json.dumps(solution))
# End of CPMPy script


A hiker is planning a trip and needs to decide which items to take in their backpack. Each item has a certain value and weight, and the hiker wants to maximize the total value of the items in the backpack without exceeding the weight capacity of the backpack.  Print which items to take (x).

# Data
values = [4, 2, 3, 7, 1]  # Values of the items
weights = [3, 1, 2, 5, 4]  # Weights of the items
capacity = 7  # Capacity of the knapsack
# End of data

# Import libraries
from cpmpy import *
import json

# Construct the model
x = boolvar(shape=len(values), name="x")

model = Model(
    sum(x * weights) <= capacity,
    maximize=sum(x * values)
)

# Solve the model
model.solve()

# Print the solution
solution = {
    "x": x.value().tolist()
}
print(json.dumps(solution))
# End of CPMPy script


Mario Problem  Mario needs to collect as much gold as possible by visiting different houses. He starts at Mario's house and ends at Luigi's house. Each house has a certain amount of gold and the travel between houses consumes fuel. Mario has a limited amount of fuel, and he needs to plan his route to maximize the gold collected without exceeding the fuel limit.  Print the succeeding houses for each house (s) as a list of 15 integers.


# Source description: https://minesweeper.online

Minesweeper rules are very simple. The board is divided into cells, with mines randomly distributed. To win, you need to open all the cells. The number on a cell shows the number of mines adjacent to it. Using this information, you can determine cells that are safe, and cells that contain mines. Cells suspected of being mines can be marked with a flag using the right mouse button.  The task is to determine which 'X' fields contain mines based on the provided numerical clues.  Print whether each cell is a mine or not (mines).

# Data
X = -1
game_data = [  # 0-8: number of mines around, -1: not opened
    [2, 3, X, 2, 2, X, 2, 1],
    [X, X, 4, X, X, 4, X, 2],
    [X, X, X, X, X, X, 4, X],
    [X, 5, X, 6, X, X, X, 2],
    [2, X, X, X, 5, 5, X, 2],
    [1, 3, 4, X, X, X, 4, X],
    [0, 1, X, 4, X, X, X, 3],
    [0, 1, 2, X, 2, 3, X, 2]
]
# End of data

# Import libraries
from cpmpy import *
import numpy as np
import json

# Parameters
game = np.array(game_data)
rows, cols = game.shape
S = [-1, 0, 1]  # for the neighbors of a cell

# Decision Variables
mines = boolvar(shape=game.shape)  # True: mine, False: not mine

model = Model()
for (r, c), val in np.ndenumerate(game):
    if val != X:
        # This cell cannot be a mine
        model += mines[r, c] == 0
        # Count neighbors
        model += (sum(mines[r + a, c + b]
                      for a in S for b in S
                      if 0 <= r + a < rows and 0 <= c + b < cols and (a, b) != (0, 0))
                  == val)

# Solve
model.solve()

# Print
solution = {"mines": mines.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


N-Puzzle Problem  The N-Puzzle is a classic sliding puzzle game where the goal is to move tiles on a grid to achieve a specific end configuration. The puzzle consists of a grid with \( n+1 \) tiles, one of which is empty. The objective is to trace the steps to the original picture by moving the tiles into their correct positions.  Input is the start and end state of the puzzle, along with the number of steps to the solution.  Print the state of the puzzle at each step (steps) as a list of N integer 2-dimensional matrices.

# Data
N = 20  # Number of steps to the solution
puzzle_start = [  # Start state of the puzzle, 0 represents the empty tile
    [0, 3, 6],
    [2, 4, 8],
    [1, 7, 5]
]

puzzle_end = [  # End state of the puzzle
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 0]
]
# End of data

# Import libraries
from cpmpy import *
import json
import numpy as np

def n_puzzle(puzzle_start, puzzle_end, N):

    m = Model()

    (dim,dim2) = puzzle_start.shape
    assert (dim == dim2), "puzzle needs square shape"
    n = dim*dim2 - 1  # e.g. an 8-puzzle

    # State of puzzle at every step
    x = intvar(0,n, shape=(N,dim,dim), name="x")

    # Start state constraint
    m += (x[0] == puzzle_start)

    # End state constraint
    m += (x[-1] == puzzle_end)

    # define neighbors = allowed moves for the '0'
    def neigh(i,j):
        # same, left,right, down,up, if within bounds
        for (rr, cc) in [(0,0),(-1,0),(1,0),(0,-1),(0,1)]:
            if 0 <= i+rr and i+rr < dim and 0 <= j+cc and j+cc < dim:
                yield (i+rr,j+cc)

    # Transition: define next based on prev + invariants
    def transition(m, prev_x, next_x):
        # for each position, determine its reachability
        for i in range(dim):
            for j in range(dim):
                m += (next_x[i,j] == 0).implies(any(prev_x[r,c] == 0 for r,c in neigh(i,j)))

        # Invariant: in each step, all cells are different
        m += AllDifferent(next_x)

        # Invariant: only the '0' position can move
        m += ((prev_x == 0) | (next_x == 0) | (prev_x == next_x))

    # apply transitions (0,1) (1,2) (2,3) ...
    for i in range(1, N):
        transition(m, x[i-1], x[i])

    return (m,x)

# Example usage
model, steps = n_puzzle(np.array(puzzle_start), np.array(puzzle_end), N)
model.solve()

# Print
solution = {"steps": steps.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source Description: https://github.com/Alexander-Schiendorfer/cp-examples/tree/main/packing

The rectangular packing problem involves placing a set of rectangular items into a larger rectangular area such that no items overlap and all items are within the boundaries of the larger area. The objective is to minimize the total area of the larger rectangle required to pack all the items.  Given the widths and heights of the items, determine their positions within the larger rectangle while ensuring no overlap and minimizing the total area.  Print the positions of the items (pos_x, pos_y) and the dimensions of the larger rectangle (total_x, total_y).

# Data
widths = [3, 4, 2, 1]  # Widths of the items
heights = [2, 3, 1, 4]  # Heights of the items
# End of data

# Import libraries
from cpmpy import *
import json
from cpmpy.expressions.utils import all_pairs

def model_packing_rectangular(widths, heights):
    # Number of different items
    n = len(widths)

    # max dimensions of the whole area
    area_min_x, area_max_x = max(widths), sum(widths)
    area_min_y, area_max_y = max(heights), sum(heights)

    # Decision variables
    pos_x = intvar(0, area_max_x, shape=n)
    pos_y = intvar(0, area_max_y, shape=n)

    total_x = intvar(area_min_x, area_max_x)
    total_y = intvar(area_min_y, area_max_y)

    m = Model()

    ## Necessary constraints
    # Every item has to be within the overall area
    m += [pos_x + widths <= total_x,
          pos_y + heights <= total_y]

    # No-overlap: every item has to be fully above, below or next to every other item
    for i, j in all_pairs(range(n)):
        m += ((pos_x[i] + widths[i] <= pos_x[j]) |
              (pos_x[j] + widths[j] <= pos_x[i]) |
              (pos_y[i] + heights[i] <= pos_y[j]) |
              (pos_y[j] + heights[j] <= pos_y[i]))

    # Minimize wrt the overall area
    m.minimize(total_x * total_y)

    ## Optional constraints
    # The needed space needs to be wider than taller
    # m += (total_x > total_y),

    # The needed space has to have a width larger than 10
    # m += (total_x >= 10),

    # The needed space has to have a height larger than 10
    # m += (total_y >= 10)

    return m, (pos_x, pos_y, total_x, total_y)

# Example usage
model, (pos_x, pos_y, total_x, total_y) = model_packing_rectangular(widths, heights)
model.solve()

# Print
solution = {
    "pos_x": pos_x.value().tolist(),
    "pos_y": pos_y.value().tolist(),
    "total_x": total_x.value(),
    "total_y": total_y.value()
}
print(json.dumps(solution))
# End of CPMPy script


The resource-constrained project scheduling problem involves scheduling a set of jobs, each with a specific duration and resource requirement, such that the total project duration (makespan) is minimized. Each job may have precedence constraints, meaning certain jobs must be completed before others can start. Additionally, there are multiple types of resources, each with a limited capacity that must not be exceeded at any time.  Given the durations of the jobs, their resource requirements, the capacities of the resources, and the precedence constraints between the jobs, determine the start times of the jobs that minimize the makespan while satisfying all constraints.  Print the start times of the jobs (start_time).

# Data
durations_data = [0, 3, 2, 5, 4, 2, 3, 4, 2, 4, 6, 0]
resource_needs_data = [[0, 0], [5, 1], [0, 4], [1, 4], [1, 3], [3, 2], [3, 1], [2, 4], [4, 0], [5, 2], [2, 5], [0, 0]]
resource_capacities_data = [6, 8]
successors_link_data = [[0, 1], [0, 2], [0, 3], [1, 4], [1, 5], [2, 9], [2, 10], [3, 8], [4, 6], [4, 7], [5, 9], [5, 10], [6, 8], [6, 9], [7, 8], [8, 11], [9, 11], [10, 11]]
# End of data

# Import libraries
from cpmpy import *
import numpy as np
import json

# Parameters
durations = np.array(durations_data)
resource_needs = np.array(resource_needs_data)
resource_capacities = np.array(resource_capacities_data)
successors_link = np.array(successors_link_data)

nb_resource = len(resource_capacities)
nb_jobs = len(durations)
max_duration = sum(durations)  # dummy upper bound, can be improved of course

# Decision Variables
start_time = intvar(0, max_duration, shape=nb_jobs)  # start time of each job

model = Model()

# Precedence constraints
for j in range(successors_link.shape[0]):
    model += start_time[successors_link[j, 1]] >= start_time[successors_link[j, 0]]+durations[successors_link[j, 0]]

# Cumulative resource constraint
for r in range(nb_resource):
    model += Cumulative(start=start_time, duration=durations, end=start_time+durations,
                        demand=resource_needs[:, r], capacity=resource_capacities[r])

makespan = max(start_time)
model.minimize(makespan)

# Solve
model.solve()

# Print
solution = {"start_time": start_time.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


The room assignment problem involves assigning a set of requests to a limited number of rooms such that each request is assigned to one room for its entire duration. Some requests may already have a room pre-assigned. The main constraint is that a room can only serve one request at a time, meaning no overlapping requests can be assigned to the same room.  Print the room assignments for the requests (room_assignments).

# Data
max_rooms = 5  # Maximum number of rooms available
start_data = ["2024-05-01", "2024-05-02", "2024-05-03", "2024-05-04"]  # Start date of the requests
end_data = ["2024-05-05", "2024-05-06", "2024-05-07", "2024-05-08"]  # End date of the requests
preassigned_room_data = [3, -1, -1, -1]  # Room 3 pre-assigned for the first request, -1 for no pre-assignment. So, the second, third, and fourth requests have no pre-assigned rooms.
# End of data

# Import libraries
import pandas as pd
from cpmpy import *
import numpy as np
import json

# Parameters
data = {
    "start": pd.to_datetime(start_data),
    "end": pd.to_datetime(end_data),
    # convert 0 to NaN
    "room": [r if r != -1 else np.nan for r in preassigned_room_data]
}
df = pd.DataFrame(data)

def model_rooms(df, max_rooms):
    n_requests = len(df)

    # All requests must be assigned to one out of the rooms (same room during entire period).
    requestvars = intvar(0, max_rooms - 1, shape=(n_requests,))

    m = Model()

    # Some requests already have a room pre-assigned
    for idx, row in df.iterrows():
        if not pd.isna(row['room']):
            m += (requestvars[idx] == int(row['room']))

    # A room can only serve one request at a time.
    # <=> requests on the same day must be in different rooms
    for day in pd.date_range(min(df['start']), max(df['end'])):
        overlapping = df[(df['start'] <= day) & (day < df['end'])]
        if len(overlapping) > 1:
            m += AllDifferent(requestvars[overlapping.index])

    return m, requestvars

# Example usage
model, room_assignments = model_rooms(df, max_rooms)
model.solve()

# Print
solution = {"room_assignments": room_assignments.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Send More Money Puzzle  The "Send More Money" puzzle is a classic cryptarithmetic problem where each letter represents a unique digit. The goal is to assign digits to the letters such that the following equation holds true:     SEND  + MORE  ------   MONEY  Each letter must be assigned a unique digit from 0 to 9, and the first letter of each word cannot be zero.  Print the values assigned to each letter (s, e, n, d, m, o, r, y).

# Import libraries
from cpmpy import *
import numpy as np
import json

# Decision variables
s, e, n, d, m, o, r, y = intvar(0, 9, shape=8)

model = Model(
    AllDifferent([s, e, n, d, m, o, r, y]),
    (sum([s, e, n, d] * np.array([1000, 100, 10, 1])) \
     + sum([m, o, r, e] * np.array([1000, 100, 10, 1])) \
     == sum([m, o, n, e, y] * np.array([10000, 1000, 100, 10, 1]))),
    s > 0,
    m > 0,
)

# Solve
model.solve()

# Print
solution = {
    "s": s.value(),
    "e": e.value(),
    "n": n.value(),
    "d": d.value(),
    "m": m.value(),
    "o": o.value(),
    "r": r.value(),
    "y": y.value()
}
print(json.dumps(solution))
# End of CPMPy script


Set (stylized as SET or SET!) is a real-time card game designed by Marsha Falco in 1974 and published by Set Enterprises in 1991. The deck consists of 81 unique cards that vary in four features across three possibilities for each kind of feature: number of shapes (one, two, or three), shape (diamond, squiggle, oval), shading (solid, striped, or open), and color (red, green, or purple).[2] Each possible combination of features (e.g. a card with three striped green diamonds) appears as a card precisely once in the deck.  In the game, certain combinations of three cards are said to make up a "set". For each one of the four categories of features—color, number, shape, and shading—the three cards must display that feature as either a) all the same, or b) all different. Put another way: For each feature the three cards must avoid having two cards showing one version of the feature and the remaining card showing a different version.  For example, 3 solid red diamonds, 2 solid green squiggles, and 1 solid purple oval form a set, because the shadings of the three cards are all the same, while the numbers, the colors, and the shapes among the three cards are all different.  For any set, the number of features that are constant (the same on all three cards) and the number of features that differ (different on all three cards) may break down as: all 4 features differing; or 1 feature being constant and 3 differing; or 2 constant and 2 differing; or 3 constant and 1 differing. (All 4 features being constant would imply that the three cards in the set are identical, which is impossible since no cards in the Set deck are identical.)  Print the indices of the winning set of cards (winning_cards).

# Data
# Constants
numbers = ONE, TWO, THREE = 1, 2, 3
colors = RED, PURPLE, GREEN = 1, 2, 3
shapes = DIAMOND, RECT, ELLIPSE = 1, 2, 3
fills = FULL, EMPTY, STRIPED = 1, 2, 3

# Make the card deck
cards_data = [
    [ONE, EMPTY, GREEN, DIAMOND],
    [TWO, STRIPED, RED, RECT],
    [THREE, STRIPED, GREEN, DIAMOND],
    [THREE, FULL, RED, DIAMOND],
    [ONE, STRIPED, GREEN, DIAMOND],
    [ONE, EMPTY, RED, DIAMOND],
    [TWO, FULL, PURPLE, DIAMOND],
    [THREE, FULL, PURPLE, ELLIPSE],
    [THREE, FULL, GREEN, RECT],
    [ONE, FULL, PURPLE, DIAMOND],
    [ONE, STRIPED, PURPLE, DIAMOND],
    [ONE, FULL, GREEN, RECT]
]
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
cards = cpm_array(cards_data)
given_numbers, given_fills, given_colors, given_shapes = cards[:, 0], cards[:, 1], cards[:, 2], cards[:, 3]

# decision variables
n_cards = len(cards)
winning_cards = intvar(0, n_cards - 1, shape=3, name="winning")  # indices of the winning cards
c1, c2, c3 = winning_cards

# constraints
model = Model()
model += AllDifferent(winning_cards)

# numbers must be all equal or all different
winning_nbs = [given_numbers[c1], given_numbers[c2], given_numbers[c3]]
model += AllEqual(winning_nbs) | AllDifferent(winning_nbs)

# filling pattern must be all equal or all different
winning_fills = [given_fills[c1], given_fills[c2], given_fills[c3]]
model += AllEqual(winning_fills) | AllDifferent(winning_fills)

# colors must be all equal or all different
winning_colors = [given_colors[c1], given_colors[c2], given_colors[c3]]
model += AllEqual(winning_colors) | AllDifferent(winning_colors)

# shapes must be all equal or all different
winning_shapes = [given_shapes[c1], given_shapes[c2], given_shapes[c3]]
model += AllEqual(winning_shapes) | AllDifferent(winning_shapes)

# Solve
model.solve()

# Print
solution = {"winning_cards": winning_cards.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


The goal of Sudoku is to fill a 9x9 grid with numbers so that each row, column and 3x3 section contain all the digits between 1 and 9.  Input is a grid with some cells filled with numbers and some empty.  Print the solved grid (grid).

# Data
input_grid = [  # 0 represents empty cells
    [0, 0, 0,  2, 0, 5,  0, 0, 0],
    [0, 9, 0,  0, 0, 0,  7, 3, 0],
    [0, 0, 2,  0, 0, 9,  0, 6, 0],

    [2, 0, 0,  0, 0, 0,  4, 0, 9],
    [0, 0, 0,  0, 7, 0,  0, 0, 0],
    [6, 0, 9,  0, 0, 0,  0, 0, 1],

    [0, 8, 0,  4, 0, 0,  1, 0, 0],
    [0, 6, 3,  0, 0, 0,  0, 8, 0],
    [0, 0, 0,  6, 0, 8,  0, 0, 0]]
# End of data

# Import libraries
from cpmpy import *
import json
import numpy as np

# Parameters
e = 0  # value for empty cells
given = np.array(input_grid)  # numpy array for easy indexing

# Decision Variables
grid = intvar(1, 9, shape=given.shape, name="grid")

# Model
model = Model(
    # Constraints on given cells (non-empty)
    grid[given != e] == given[given != e],  # numpy's indexing, vectorized equality
    # Constraints on rows and columns
    [AllDifferent(row) for row in grid],
    [AllDifferent(col) for col in grid.T],  # numpy's transpose
)

# Constraints on blocks
for i in range(0, 9, 3):
    for j in range(0, 9, 3):
        model += AllDifferent(grid[i:i+3, j:j+3])  # python's indexing

# Solve
model.solve()

# Print
solution = {"grid": grid.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


The travelling salesman problem, also known as the travelling salesperson problem (TSP), asks the following question: "Given a list of cities and the distances between each pair of cities (or the locations of the cities), what is the shortest possible route that visits each city exactly once and returns to the origin city?  Print the optimal travel distance (travel_distance).

# Data
locations = [
    (288, 149), (288, 129), (270, 133), (256, 141), (256, 163), (246, 157),
    (236, 169), (228, 169), (228, 148), (220, 164), (212, 172), (204, 159)
]
# End of data

# Import libraries
import math
import numpy as np
from cpmpy import *
import json

# Parameters
def compute_euclidean_distance_matrix(locations):
    """Computes distances between all points (from ortools docs)."""
    n_city = len(locations)
    distances = np.zeros((n_city, n_city))
    for from_counter, from_node in enumerate(locations):
        for to_counter, to_node in enumerate(locations):
            if from_counter != to_counter:
                distances[from_counter][to_counter] = (int(
                    math.hypot((from_node[0] - to_node[0]),
                               (from_node[1] - to_node[1]))))
    return distances.astype(int)

distance_matrix = compute_euclidean_distance_matrix(locations)
n_city = len(locations)

# we use the successor variable formulation and circuit global constraint here
# alternative is to model like in vrp.py

# x[i]=j means that j is visited immediately after i
x = intvar(0, n_city - 1, shape=n_city)

# The 'circuit' global constraint ensures that the successor variables from a circuit
model = Model(Circuit(x))

# the objective is to minimize the travelled distance
distance_matrix = cpm_array(distance_matrix)  # for indexing with variable
travel_distance = sum(distance_matrix[i, x[i]] for i in range(n_city))
model.minimize(travel_distance)

# Solve
model.solve()

# Print
solution = {"travel_distance": int(travel_distance.value())}
print(json.dumps(solution))


Someone in Dreadsbury Mansion killed Aunt Agatha. Agatha, the butler, and Charles live in Dreadsbury Mansion, and are the only ones to live there. A killer always hates, and is no richer than his victim. Charles hates noone that Agatha hates. Agatha hates everybody except the butler. The butler hates everyone not richer than Aunt Agatha. The butler hates everyone whom Agatha hates. Noone hates everyone. Who killed Agatha?  Print the index of the killer (killer).

# Data
names = ["Agatha herself", "the butler", "Charles"]
# End of data

# Import libraries
from cpmpy import *
import json

# Agatha, the butler, and Charles live in Dreadsbury Mansion, and
# are the only ones to live there.
n = 3
(agatha, butler, charles) = range(n)  # enum constants

# Who killed agatha?
victim = agatha
killer = intvar(0, n - 1, name="killer")  # 0=Agatha, 1=butler, 2=Charles

hates = boolvar(shape=(n, n), name="hates")
richer = boolvar(shape=(n, n), name="richer")

model = Model(
    # A killer always hates, and is no richer than, his victim.
    # note; 'killer' is a variable, so must write ==1/==0 explicitly
    hates[killer, victim] == 1,
    richer[killer, victim] == 0,

    # implied richness: no one richer than himself, and anti-reflexive
    [~richer[i, i] for i in range(n)],
    [(richer[i, j]) == (~richer[j, i]) for i in range(n) for j in range(i + 1, n)],

    # Charles hates noone that Agatha hates.
    [(hates[agatha, i]).implies(~hates[charles, i]) for i in range(n)],

    # Agatha hates everybody except the butler.
    hates[agatha, (agatha, charles, butler)] == [1, 1, 0],

    # The butler hates everyone not richer than Aunt Agatha.
    [(~richer[i, agatha]).implies(hates[butler, i]) for i in range(n)],

    # The butler hates everyone whom Agatha hates.
    [(hates[agatha, i]).implies(hates[butler, i]) for i in range(n)],

    # Noone hates everyone.
    [sum(hates[i, :]) <= 2 for i in range(n)],
)

# Solve
model.solve()

# Print
solution = {"killer": killer.value()}
print(json.dumps(solution))
# End of CPMPy script


The wolf, goat and cabbage problem is a river crossing puzzle. It dates back to at least the 9th century, and has entered the folklore of several cultures.  The story: A farmer with a wolf, a goat, and a cabbage must cross a river by boat. The boat can carry only the farmer and a single item. If left unattended together, the wolf would eat the goat, or the goat would eat the cabbage. How can they cross the river without anything being eaten?  Print whether the wolf, goat, cabbage, and boat are on the destination shore (1) or the starting shore (0) at each stage (wolf_pos, goat_pos, cabbage_pos, boat_pos).

# Data
stage = 8  # Number of stages
# End of data

# Import libraries
from cpmpy import *
import json

wolf_pos = boolvar(stage)
cabbage_pos = boolvar(stage)
goat_pos = boolvar(stage)
boat_pos = boolvar(stage)

model = Model(
    # Initial situation
    (boat_pos[0] == 0),
    (wolf_pos[0] == 0),
    (goat_pos[0] == 0),
    (cabbage_pos[0] == 0),

    # Boat keeps moving between shores
    [boat_pos[i] != boat_pos[i - 1] for i in range(1, stage)],

    # Final situation
    (boat_pos[-1] == 1),
    (wolf_pos[-1] == 1),
    (goat_pos[-1] == 1),
    (cabbage_pos[-1] == 1),

    # # Wolf and goat cannot be left alone
    [(goat_pos[i] != wolf_pos[i]) | (boat_pos[i] == wolf_pos[i]) for i in range(stage)],

    # # Goat and cabbage cannot be left alone
    [(goat_pos[i] != cabbage_pos[i]) | (boat_pos[i] == goat_pos[i]) for i in range(stage)],

    # # Only one animal/cabbage can move per turn
    [abs(wolf_pos[i] - wolf_pos[i + 1]) + abs(goat_pos[i] - goat_pos[i + 1]) + abs(
        cabbage_pos[i] - cabbage_pos[i + 1]) <= 1 for i in range(stage - 1)],
)

# Solve
model.solve()

# Print
solution = {"wolf_pos": wolf_pos.value().tolist(), "goat_pos": goat_pos.value().tolist(),
            "cabbage_pos": cabbage_pos.value().tolist(), "boat_pos": boat_pos.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Misc: Based on PyCSP3's Zebra.py by C. Lecoutre, https://github.com/xcsp3team/pycsp3/blob/master/problems/csp/single/Zebra.py

The Zebra puzzle (sometimes referred to as Einstein's puzzle) is defined as follows. There are five houses in a row, numbered from left to right. Each of the five houses is painted a different color, and has one inhabitant. The inhabitants are all of different nationalities, own different pets, drink different beverages and have different jobs. We know that:  - colors are yellow, green, red, white, and blue  - nations of inhabitants are italy, spain, japan, england, and norway  - pets are cat, zebra, bear, snails, and horse  - drinks are milk, water, tea, coffee, and juice  - jobs are painter, sculptor, diplomat, pianist, and doctor  - the painter owns the horse  - the diplomat drinks coffee  - the one who drinks milk lives in the white house  - the Spaniard is a painter  - the Englishman lives in the red house  - the snails are owned by the sculptor  - the green house is on the left of the red one  - the Norwegian lives on the right of the blue house  - the doctor drinks milk  - the diplomat is Japanese  - the Norwegian owns the zebra  - the green house is next to the white one  - the horse is owned by the neighbor of the diplomat  - the Italian either lives in the red, white or green house  Print the numbers representing the house of each inhabitant (colors, nations, jobs, pets, drinks). The same number represents the same house.

# Import libraries
from cpmpy import *
import json

n_houses = 5

# colors[i] is the house of the ith color
yellow, green, red, white, blue = colors = intvar(0,n_houses-1, shape=n_houses)

# nations[i] is the house of the inhabitant with the ith nationality
italy, spain, japan, england, norway = nations = intvar(0,n_houses-1, shape=n_houses)

# jobs[i] is the house of the inhabitant with the ith job
painter, sculptor, diplomat, pianist, doctor = jobs = intvar(0,n_houses-1, shape=n_houses)

# pets[i] is the house of the inhabitant with the ith pet
cat, zebra, bear, snails, horse = pets = intvar(0,n_houses-1, shape=n_houses)

# drinks[i] is the house of the inhabitant with the ith preferred drink
milk, water, tea, coffee, juice = drinks = intvar(0,n_houses-1, shape=n_houses)

model = Model(
    AllDifferent(colors),
    AllDifferent(nations),
    AllDifferent(jobs),
    AllDifferent(pets),
    AllDifferent(drinks),

    painter == horse,
    diplomat == coffee,
    white == milk,
    spain == painter,
    england == red,
    snails == sculptor,
    green + 1 == red,
    blue + 1 == norway,
    doctor == milk,
    japan == diplomat,
    norway == zebra,
    abs(green - white) == 1,
    #horse in {diplomat - 1, diplomat + 1},
    (horse == diplomat-1)|(horse == diplomat+1),
    #italy in {red, white, green}
    (italy == red)|(italy == white)|(italy == green),
)
model.solve()

# Print
solution = {
    "colors": colors.value().tolist(),
    "nations": nations.value().tolist(),
    "jobs": jobs.value().tolist(),
    "pets": pets.value().tolist(),
    "drinks": drinks.value().tolist(),
}
print(json.dumps(solution))
# End of CPMPy script


My bank card has a 4 digit pin, abcd. I use the following facts to help me remember it:  - no two digits are the same - the 2-digit number cd is 3 times the 2-digit number ab - the 2-digit number da is 2 times the 2-digit number bc  Print the PIN (a, b, c, d).

# Import libraries
from cpmpy import *
import json

# Decision Variables
a, b, c, d = intvar(1, 9, shape=4)  # a, b, c, d are the four digits of the PIN

# Constraints
model = Model()

model += AllDifferent([a, b, c, d])  # no two digits are the same
model += 10 * c + d == 3 * (10 * a + b)  # cd is 3 times ab
model += 10 * d + a == 2 * (10 * b + c)  # da is 2 times bc

# Solve
model.solve()

# Print the solution
solution = {"a": a.value(), "b": b.value(), "c": c.value(), "d": d.value()}
print(json.dumps(solution))
# End of CPMPy script


We want to climb a stair of n steps with [m1, m2] steps at a time. For example a stair of 4 steps with m1 = 1, and m2 = 2 can be climbed with a sequence of four one-step moves or with two two-steps moves. Find a way to climb a stair of 20 steps with m1 = 3 and m2 = 5, i.e. you can take only 3 or 4 or 5 steps at a time.  Print the number of steps (steps) taken at each move as a list of 20 integers, where each integer is between 3 and 5, or 0 if no steps are taken at that move, i.e. after we reach the top of the stair with the last move.

# Import libraries
from cpmpy import *
import json
import numpy as np

# Parameters
n = 20  # total number of steps in the stair
m1, m2 = 3, 5  # number of steps that can be taken at a time

# Decision variables
# In the worst case, we take all steps one at a time, so we have 'n' decision variables
steps = intvar(0, m2, shape=n)  # steps taken at each move

# Model setup
model = Model()

# Constraint: the sum of steps should equal the total number of stairs
model += sum(steps) == n

# Constraint: the number of steps taken at each move should be between m1 and m2 or 0
model += [(steps[i] >= m1) | (steps[i] == 0) for i in range(n)]
model += [steps[i] <= m2 for i in range(n)]

# Trailing zeros: If a step is 0, then all the following steps should be 0
for i in range(1, n):
    model += (steps[i - 1] == 0).implies(all(steps[j] == 0 for j in range(i, n)))

# Solve
model.solve()

# Print the solution
solution = {"steps": steps.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


We want to assign a different colour to the following countries: Belgium, Denmark, France, Germany, Netherlands and Luxembourg. Two neighbouring countries cannot have the same colour. You can use integers starting from 1 to represent the colours. Find a colouring that minimizes the number of colours used.  Print the colours assigned to each country as a list (colors).

# Data
graph = [  # the adjacency of the countries, (i, j) means that country i is adjacent to country j
    [3, 1],
    [3, 6],
    [3, 4],
    [6, 4],
    [6, 1],
    [1, 5],
    [1, 4],
    [4, 5],
    [4, 2]
]
# End of data

# Import libraries
from cpmpy import *
import json

num_edges = 9
num_nodes = 6

# Decision Variables
colors = intvar(1, num_nodes, shape=num_nodes)  # the colour assigned to each country

# Constraints
model = Model()

# Two neighbouring countries cannot have the same colour
for i, j in graph:
    # Python uses 0-based indexing, but the countries are 1-based, so we need to subtract 1 from the indices
    model += colors[i - 1] != colors[j - 1]

# Objective
# Find a colouring that minimizes the number of colours used
model.minimize(max(colors))

# Solve
model.solve()

# Print the solution
solution = {"colors": colors.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


In preparation for Passover, five children at Hebrew school (Bernice, Carl, Debby, Sammy, and Ted) have been chosen to present different parts of the story of the Exodus from Egypt (burning bush, captivity, Moses’s youth, Passover, or the Ten Commandments). Each child is a different age (three, five, seven, eight, or ten), and the family of each child has recently made its own exodus to America from a different country (Ethiopia, Kazakhstan, Lithuania, Morocco, or Yemen). Can you find the age of each child, his or her family’s country of origin, and the part of the Exodus story each related? 1. Debby’s family is from Lithuania. 2. The child who told the story of the Passover is two years older than Bernice. 3. The child whose family is from Yemen is younger than the child from the Ethiopian family. 4. The child from the Moroccan family is three years older than Ted. 5. Sammy is three years older than the child who told the story of Moses’s youth in the house of the Pharaoh. Determine the association: Age-Child-Country-Story.  Print the ages (ages), children (children), countries (countries), and stories (stories) as lists of integers from 1 to 5, where the same number represents a mapping between the four categories.

# Import libraries
from cpmpy import *
import json

# Decision variables
# We model the ages, children, countries, and stories as integer variables with values from 1 to 5.
age3, age5, age7, age8, age10 = ages = intvar(1, 5, shape=5)
bernice, carl, debby, sammy, ted = children = intvar(1, 5, shape=5)
ethiopia, kazakhstan, lithuania, morocco, yemen = countries = intvar(1, 5, shape=5)
burning_bush, captivity, moses_youth, passover, ten_commandments = stories = intvar(1, 5, shape=5)

# Constraints
model = Model()

# All entities are different per category
model += AllDifferent(ages)
model += AllDifferent(children)
model += AllDifferent(countries)
model += AllDifferent(stories)

# Debby’s family is from Lithuania.
model += debby == lithuania

# The child who told the story of the Passover is two years older than Bernice.
# So, we will add constraints for all possible pairs of ages to enforce this relationship.
age_to_int = {age3: 3, age5: 5, age7: 7, age8: 8, age10: 10}
model += [((a1 == passover) & (a2 == bernice)).implies(age_to_int[a1] == age_to_int[a2] + 2)
          for a1 in ages for a2 in ages]

# The child whose family is from Yemen is younger than the child from the Ethiopian family.
model += [((a1 == yemen) & (a2 == ethiopia)).implies(age_to_int[a1] < age_to_int[a2])
          for a1 in ages for a2 in ages]

# The child from the Moroccan family is three years older than Ted.
model += [((a1 == morocco) & (a2 == ted)).implies(age_to_int[a1] == age_to_int[a2] + 3)
          for a1 in ages for a2 in ages]

# Sammy is three years older than the child who told the story of Moses’s youth in the house of the Pharaoh.
model += [((a1 == sammy) & (a2 == moses_youth)).implies(age_to_int[a1] == age_to_int[a2] + 3)
          for a1 in ages for a2 in ages]

# Solve
model.solve()

# Print the solution
solution = {
    "ages": ages.value().tolist(),
    "children": children.value().tolist(),
    "countries": countries.value().tolist(),
    "stories": stories.value().tolist()
}
print(json.dumps(solution))
# End of CPMPy script


A farmer has 25 cows numbered 1 to 25. number 1 cow gives 1kg milk, number 2 gives 2 kg... number and so on up to number 25 that gives 25 kg per day. The farmer has 5 sons and he wants to distribute his cows to them: 7 to the first, 6 to the second and so on down to 3 to the last, however, the total quantity of milk produced should be the same: how can he distribute the cows?  Print the assignment of cows to sons (cow_assignments), as a list of 25 integers from 0 to 4.

# Import libraries
from cpmpy import *
import json

# Parameters
num_cows = 25
num_sons = 5
milk_per_cow = list(range(1, num_cows + 1))
cows_per_son = [7, 6, 5, 4, 3]

total_milk = sum(milk_per_cow)  # Total milk produced by all cows
total_milk_per_son = total_milk // num_sons  # Total milk each son should get

# Decision variables
# Each cow is assigned to a son, represented by 0-4
cow_assignments = intvar(0, num_sons - 1, shape=num_cows)

# Constraints
model = Model()

# Each son gets a specific number of cows
for son in range(num_sons):
    model += sum(cow_assignments == son) == cows_per_son[son]

# The total milk production for each son is equal
for son in range(num_sons):
    model += sum(milk_per_cow[i] * (cow_assignments[i] == son) for i in range(num_cows)) == total_milk_per_son

# Solve
model.solve()

# Print the solution
solution = {"cow_assignments": cow_assignments.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Baker, Cooper, Fletcher, Miller, and Smith live on the first five floors of an apartment house. Baker does not live on the fifth floor. Cooper does not live on the first floor. Fletcher does not live on either the fifth or the first floor. Miller lives on a higher floor than does Cooper. Smith does not live on a floor adjacent to Fletcher’. Fletcher does not live on a floor adjacent to Cooper’s. They all live on different floors. Find the floors where these people live.  Print the floors where each person lives (B, C, F, M, S).

# Import libraries
from cpmpy import *
import json

# Decision Variables
B = intvar(1, 5)  # Baker
C = intvar(1, 5)  # Cooper
F = intvar(1, 5)  # Fletcher
M = intvar(1, 5)  # Miller
S = intvar(1, 5)  # Smith

# Constraints
model = Model()

model += B != 5  # Baker does not live on the fifth floor
model += C != 1  # Cooper does not live on the first floor
model += (F != 5) & (F != 1)  # Fletcher does not live on either the fifth or the first floor
model += M > C  # Miller lives on a higher floor than does Cooper
model += abs(S - F) != 1  # Smith does not live on a floor adjacent to Fletcher
model += abs(F - C) != 1  # Fletcher does not live on a floor adjacent to Cooper
model += AllDifferent([B, C, F, M, S])  # They all live on different floors

# Solve
model.solve()

# Print the solution
solution = {"B": B.value(), "C": C.value(), "F": F.value(), "M": M.value(), "S": S.value()}
print(json.dumps(solution))
# End of CPMPy script


A kid goes into a grocery store and buys four items. The cashier charges $7.11, the kid pays and is about to leave when the cashier calls the kid back, and says "Hold on, I multiplied the four items instead of adding them; I’ll try again; Hah, with adding them the price still comes to $7.11". What were the prices of the four items?  Print the prices of the four items (prices) in cents.

# Import libraries
from cpmpy import *
import json
import numpy as np

# Parameters
total_price = 711  # total price in cents
num_items = 4

# Decision variables (prices are considered in cents)
prices = intvar(1, total_price, shape=num_items)

# Constraints
model = Model()

# The sum of the prices in cents is equal to the total price in cents
model += sum(prices) == total_price

# The product of the prices should equal to the scaled total price in cents (to account for the multiplication)
model += np.prod(prices) == total_price * (100 ** (num_items - 1))

# Solve
model.solve()

# Print the solution
solution = {"prices": prices.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


A boy wants to give an apple to a girl. To get to her, he has to pass through five gates, each with a guard. He bribes each guard with half of his apples, plus one. The boy does not have a knife, therefore he gives the guard an integer number of apples. After he’s given the apple to the girl, he has no apples left.  Print a list of 6 numbers (apples), containing the number of apples before each gate, plus the number of apples after the last gate.

# Import libraries
from cpmpy import *
import json

# Parameters
num_gates = 5

# Decision Variables
apples = intvar(0, 100, shape=num_gates + 1)  # the number of apples before each gate plus after the last gate

# Constraints
model = Model()

# The boy is left with no apples after giving the apple to the girl, so he has 1 apple after the last gate.
model += apples[-1] == 1

# At each guard, the boy gives half of his apples, plus one.
for i in range(1, num_gates + 1):
    has_before = apples[i - 1]
    has_after = apples[i]
    model += has_before == 2 * (has_after + 1)

# Solve
model.solve()

# Print the solution
solution = {"apples": apples.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Find a combination of 4 different numbers between 1 and 100, such that the sum of the squares of the two first numbers is equal to the sum of the squares of the other two numbers, i.e. a^2 + b^2 = c^2 + d^2 for some a, b, c, d in {1, 100}, a != b != c != d.  Print the numbers (a, b, c, d).

# Import libraries
from cpmpy import *
import json
import numpy as np

range_min = 1
range_max = 100

# Decision variables
a, b, c, d = intvar(range_min, range_max, shape=4)

# Constraints
model = Model()

# Sum of squares of any two numbers is equal to the sum of squares of the other two numbers
model += (a**2 + b**2) == (c**2 + d**2)

# Constraints to ensure all variables are distinct
model += AllDifferent([a, b, c, d])

# Solve
model.solve()

# Print the solution
solution = {"a": a.value(), "b": b.value(), "c": c.value(), "d": d.value()}
print(json.dumps(solution))
# End of CPMPy script


At the hospital n people are on a waiting list for a kidney’s transplant. We have the information about the compatibility between these people as a directed graph: compatible[i] is the set of people to which i can donate. Given this information, we want to maximize the number of people that receive a new kidney: anyone who gives a kidney must receive one, and no person receives more than one kidney.  Print the transplants (transplants) as a list of lists, where transplants[i][j] is 1 if person i donates to person j, and 0 otherwise.

# Data
num_people = 8  # number of people
compatible = [  # 1-based indexing, compatible[i] is the list of people to which i can donate
    [2, 3],
    [1, 6],
    [1, 4, 7],
    [2],
    [2],
    [5],
    [8],
    [3]
]
# End of data

# Import libraries
from cpmpy import *
import json

# Decision variables
transplants = boolvar(shape=(num_people, num_people))  # transplants[i][j] is True if i donates to j

# Model setup
model = Model()

# Constraints for the transplant pairs
for i in range(num_people):
    # Anyone who gives a kidney must receive one
    gives_kidney = sum(transplants[i, :]) >= 1
    receives_kidney = sum(transplants[:, i]) >= 1
    model += gives_kidney.implies(receives_kidney)

    # Each person can donate to at most one person and receive from at most one person
    can_donate_once = sum(transplants[i, :]) <= 1
    can_receive_once = sum(transplants[:, i]) <= 1
    model += can_donate_once & can_receive_once

    # Compatibility constraint: if i can't donate to j, then transplants[i][j] must be 0 (adjust for 0-based indexing)
    model += [transplants[i, j] == 0 for j in range(num_people) if j + 1 not in compatible[i]]

# Objective: maximize the number of transplants
model.maximize(sum(transplants))

# Solve
model.solve()

# Print the solution
solution = {"transplants": transplants.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


A magic square is an n x n grid (n != 2) such that each cell contains a different integer from 1 to n^2 and the sum of the integers in each row, column and diagonal is equal. Find a magic square for size 4, knowing that the sum of integers of each row, column and diagonal has to be equal to n(n^2+ 1)/2 (integer).  Print the magic square as a list of lists (square).

# Import libraries
from cpmpy import *
import json

# Parameters
n = 4  # size of the magic square
magic_sum = n * (n**2 + 1) // 2  # sum of each row, column and diagonal

# Decision Variables
square = intvar(1, n ** 2, shape=(n, n))  # the magic square

# Constraints
model = Model()

# All numbers in the magic square must be different
model += AllDifferent(square)

# The sum of the numbers in each row must be equal to the magic sum
for i in range(n):
    model += sum(square[i, :]) == magic_sum

# The sum of the numbers in each column must be equal to the magic sum
for j in range(n):
    model += sum(square[:, j]) == magic_sum

# The sum of the numbers in the main diagonal must be equal to the magic sum
model += sum(square[i, i] for i in range(n)) == magic_sum

# The sum of the numbers in the other diagonal must be equal to the magic sum
model += sum(square[i, n - 1 - i] for i in range(n)) == magic_sum

# Solve
model.solve()

# Print the solution
solution = {"square": square.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


In graph theory, an independent set is a set of vertices in a graph, no two of which are adjacent. A maximal independent set is an independent set that is not a subset of any other independent set. A graph may have many maximal independent sets of widely varying sizes: find a maximal independent set for the data provided. The data provides an array containing for each node of the graph the set of adjacent nodes.  Print whether each node is included in the maximal independent set (nodes).

# Data
n = 8  # number of nodes in the graph
adjacency_list = [  # adjacency list for each node in the graph
    [2, 3, 7],
    [1, 4, 8],
    [1, 4, 5],
    [2, 3, 6],
    [3, 6, 7],
    [4, 5, 8],
    [1, 5, 8],
    [2, 6, 7]
]
# End of data

# Import libraries
from cpmpy import *
import json

# Create a binary decision variable for each node to indicate if it's included in the independent set
nodes = boolvar(shape=n)

# Model setup
model = Model()

# Constraint: No two adjacent nodes can both be in the independent set
for i, neighbors in enumerate(adjacency_list):
    for neighbor in neighbors:
        # Subtract 1 to adjust for 1-based indexing in the data
        neighbor_idx = neighbor - 1
        # Ensure that for every edge, at least one of its endpoints is not in the independent set
        model += ~(nodes[i] & nodes[neighbor_idx])

# Objective: Maximize the number of nodes in the independent set
model.maximize(sum(nodes))

# Solve
model.solve()

# Print the solution
solution = {"nodes": nodes.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Alice has to give Bob change of 199 euros. She has 6 different types of coins of different value ([1, 2, 5, 10, 25, 50]) and she has a certain number of coins of each value available ([20, 10, 15, 8, 4, 2]). How can the change be composed with the available coins minimizing the number of coins used?  Print the number of coins of each type to give to Bob (coin_counts).

# Import libraries
from cpmpy import *
import json

# Parameters
amount = 199  # amount of money to give to Bob
n = 6  # number of types of coins
types_of_coins = [1, 2, 5, 10, 25, 50]  # value of each type of coin
available_coins = [20, 10, 15, 8, 4, 2]  # number of available coins of each type

# Decision Variables
coin_counts = intvar(0, 20, shape=n)  # number of coins of each type to give to Bob

# Constraints
model = Model()

# The sum of the coins given to Bob must be equal to the amount of money to give him
model += sum(coin_counts[i] * types_of_coins[i] for i in range(n)) == amount

# The number of each type of coin given to Bob must not exceed the available coins
for i in range(n):
    model += coin_counts[i] <= available_coins[i]

# Objective: Minimize the total number of coins given to Bob
model.minimize(sum(coin_counts))

# Solve
model.solve()

# Print the solution
solution = {"coin_counts": coin_counts.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


# Source description: Steven S. Skiena's The Algorithm Design Manual

Consider the following scheduling problem. Imagine you are a highly-in- demand actor, who has been presented with offers to star in n different movie projects under development. Each offer comes specified with the first and last day of filming. To take the job, you must commit to being available throughout this entire period. Thus, you cannot simultaneously accept two jobs whose intervals overlap. For an artist such as yourself, the criteria for job acceptance is clear: you want to make as much money as possible. Because each of these films pays the same fee per film, this implies you seek the largest possible set of jobs (intervals) such that no two of them conflict with each other.  Input is a list of movies along with their first and last day of filming  Print the number of movies accepted (num_selected_movies) as well as which movies are accepted (selected_movies) as a list of 0s and 1s, where 1 indicates that the movie is selected and 0 indicates that it is not selected (in the order of the input list).

# Data
movies = [  # title, start, end
    ["Tarjan of the Jungle", 4, 13],
    ["The Four Volume Problem", 17, 27],
    ["The President's Algorist", 1, 10],
    ["Steiner's Tree", 12, 18],
    ["Process Terminated", 23, 30],
    ["Halting State", 9, 16],
    ["Programming Challenges", 19, 25],
    ["Discrete Mathematics", 2, 7],
    ["Calculated Bets", 26, 31]
]
# End of data

# Import libraries
from cpmpy import *
import json

# Parameters
num_movies = len(movies)

# Decision Variables
selected_movies = boolvar(shape=num_movies, name="selected_movies")  # 1 if the movie is selected, 0 otherwise
num_selected_movies = sum(selected_movies)  # Number of selected movies

# Model
model = Model()

# Add constraint for non-overlapping movie schedules
for i in range(num_movies):
    for j in range(num_movies):
        # Check if the intervals overlap for each pair of movies
        if (i != j  # Different movies
                and movies[i][2] > movies[j][1]  # Movie i ends after movie j starts
                and movies[j][2] > movies[i][1]  # Movie j ends after movie i starts
        ):
            # Then, the movies cannot be selected together
            model += selected_movies[i] + selected_movies[j] <= 1

# Objective: Maximize the number of selected movies
model.maximize(num_selected_movies)

# Solve
model.solve()

# Print
solution = {"num_selected_movies": model.objective_value(),
            "selected_movies": selected_movies.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


There are 13 people. 4 of them are male. They randomly enter a room one at a time. Find a way that the males and females enter so that the ratio of females to males in the room at any one time is no greater than 7/3?  Print the sequence of people entering the room (sequence), where 0 represents male and 1 represents female.

# Import libraries
from cpmpy import *
import json

total_people = 13
num_males = 4

# Decision variable: 0 for male, 1 for female
sequence = boolvar(shape=total_people)

# Constraints
model = Model()

# Ensure exactly number of males and females
model += [sum(sequence) == total_people - num_males]

# Add constraints for the ratio at each point in the sequence
for i in range(1, total_people):
    total_females_so_far = sum(sequence[:i])
    total_males_so_far = i - sum(sequence[:i])
    # Number of females to males is no greater than 7/3, or 3 times the females is less than or equal to 7 times the males
    model += (3 * total_females_so_far) <= (7 * total_males_so_far)

# Solve
model.solve()

# Print the solution
solution = {"sequence": sequence.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Out of the set of integers 1,...,100 you are given ten different integers ([81 21 79  4 29 70 28 20 14  7]). From this set A of ten integers you can always find two disjoint non-empty subsets, S and T, such that the sum of elements in S equals the sum of elements in T. Note: S union T does not need to be all ten elements of A. Find sets S and T for the given set A.  Print which elements are in S (in_S) and which elements are in T (in_T), with 2 lists of length 10, where 1 means that the element is in the subset and 0 means that it is not.

# Import libraries
from cpmpy import *
import json

# Parameters
A = [81, 21, 79, 4, 29, 70, 28, 20, 14, 7]

# Decision variables: 1 if an element is in the subset, 0 otherwise
in_S = boolvar(shape=len(A))
in_T = boolvar(shape=len(A))

# Model setup
model = Model()

# Constraint: sum of elements in S equals sum of elements in T
model += (sum(in_S * A) == sum(in_T * A))

# S and T are disjoint, so there is no element that is in both S and T
model += (sum(in_S * in_T) == 0)

# S and T are non-empty
model += (sum(in_S) > 0)
model += (sum(in_T) > 0)

# Solve
model.solve()

# Print the solution
solution = {"in_S": in_S.value().tolist(), "in_T": in_T.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


A bank van had several bags of coins, each containing either 16, 17, 23, 24, 39, or 40 coins (there are multiple bags of the same kind). While the van was parked on the street, thieves stole some bags. A total of 100 coins were lost. It is required to find how many bags were stolen for each type of coin bag.  Print the number of bags stolen for each type of coins (bags).

# Import libraries
from cpmpy import *
import json

total_coins_lost = 100
coin_numbers = [16, 17, 23, 24, 39, 40]

# Decision variables
# The number of bags stolen for each type of coins
bags = intvar(0, total_coins_lost, shape=len(coin_numbers))

# Constraints
model = Model()

# The total number of coins lost is equal to the sum of the coins in the stolen bags
model += sum([bags[i] * coin_numbers[i] for i in range(len(coin_numbers))]) == total_coins_lost

# Solve
model.solve()

# Print the solution
solution = {"bags": bags.value().tolist()}
print(json.dumps(solution))
# End of CPMPy script


Following a robbery at Sparkles the Jeweller’s, Inspector Korner of the Yard interviewed six of the usual suspects. He knew that the getaway car had been barely big enough to hold two, so he reckoned that at least four of them were innocent - but which ones? He also supposed that the innocent ones would tell the truth, while the guilty one or ones would lie. What they actually said was: - ARTIE: "It wasn't me." - BILL: "Crackitt was in it up to his neck." - CRACKITT: "No I wasn't." - DODGY: "If Crackitt did it, Bill did it with him." - EDGY: "Nobody did it alone." - FINGERS: "That’s right: it was Artie and Dodgy together." If the good inspector’s suppositions were correct, who is guilty?  Print whether each suspect is guilty or not (artie, bill, crackitt, dodgy, edgy, fingers).

# Import libraries
from cpmpy import *
import json

# Decision Variables for each suspect representing if they are guilty
artie = boolvar(name="Artie")
bill = boolvar(name="Bill")
crackitt = boolvar(name="Crackitt")
dodgy = boolvar(name="Dodgy")
edgy = boolvar(name="Edgy")
fingers = boolvar(name="Fingers")
suspects = [artie, bill, crackitt, dodgy, edgy, fingers]

# Constraints
model = Model()

# At most two are guilty because the getaway car was small
model += sum(suspects) <= 2

# Statement Constraints; if the suspect is guilty, they are lying, so their statement is false

# Artie: "It wasn't me."
artie_statement = ~artie
model += artie == ~artie_statement

# Bill: "Crackitt was in it up to his neck."
bill_statement = crackitt
model += bill == ~bill_statement

# Crackitt: "No I wasn't."
crackitt_statement = ~crackitt
model += crackitt == ~crackitt_statement

# Dodgy: "If Crackitt did it, Bill did it with him."
dodgy_statement = crackitt.implies(bill)
model += dodgy == ~dodgy_statement

# Edgy: "Nobody did it alone."
edgy_statement = sum(suspects) > 1
model += edgy == ~edgy_statement

# Fingers: "That’s right: it was Artie and Dodgy together."
fingers_statement = artie & dodgy
model += fingers == ~fingers_statement

# Solve
model.solve()

# Print the solution
solution = {
    "artie": int(artie.value()),
    "bill": int(bill.value()),
    "crackitt": int(crackitt.value()),
    "dodgy": int(dodgy.value()),
    "edgy": int(edgy.value()),
    "fingers": int(fingers.value())
}
print(json.dumps(solution))
# End of CPMPy script