def golomb(n: int) -> facile.Solution: ticks = [facile.variable(range(2**n)) for i in range(n)] # First tick at the start of the ruler facile.constraint(ticks[0] == 0) # Ticks are ordered for i in range(n - 1): facile.constraint(ticks[i] < ticks[i + 1]) # All distances distances = [] for i in range(n - 1): for j in range(i + 1, n): distances.append(facile.variable(ticks[j] - ticks[i])) facile.constraint(facile.alldifferent(distances)) for d in distances: facile.constraint(d > 0) # Breaking the symmetry size = len(distances) facile.constraint(distances[size - 1] > distances[0]) return facile.minimize(ticks, ticks[n - 1], backtrack=True, on_solution=print)
def golomb(n): # On peut majorer la taille de la règle par 2 ** n. En effet, si # ticks[i] vaut 2**i alors tous les ticks[i] - ticks[j] = 2**i - 2**j # = 2**j * (2**(i-j) - 1) qui sont tous différents. # On a donc au moins cette solution. ticks = [facile.variable(range(2**n)) for i in range(n)] # First tick at the start of the ruler facile.constraint(ticks[0] == 0) # Ticks are ordered for i in range(n - 1): facile.constraint(ticks[i] < ticks[i + 1]) # All distances distances = [] for i in range(n - 1): for j in range(i + 1, n): distances.append(facile.variable(ticks[j] - ticks[i])) facile.constraint(facile.alldifferent(distances)) for d in distances: facile.constraint(d > 0) # Breaking the symmetry facile.constraint(distances[-1] > distances[0]) return (facile.minimize(ticks, ticks[n - 1], backtrack=True, on_solution=print))
def tile(sizes, bigsize): n = len(sizes) xs = [facile.variable(0, bigsize - sizes[i]) for i in range(n)] ys = [facile.variable(0, bigsize - sizes[i]) for i in range(n)] for i in range(n-1): for j in range(i+1, n): c_left = xs[j] + sizes[j] <= xs[i] # j on the left of i c_right = xs[j] >= xs[i] + sizes[i] # j on the right of i c_below = ys[j] + sizes[j] <= ys[i] # etc. c_above = ys[j] >= ys[i] + sizes[i] facile.constraint(c_left | c_right | c_below | c_above) # Redundant capacity constraint def full_line(xy): for i in range(bigsize): # equivalent to (xy[j] >= i - sizes[j] + 1) & (xy[j] <= i) intersections = \ [xy[j].in_interval(i - sizes[j] + 1, i) for j in range(n)] scal_prod = sum([s * k for s, k in zip(sizes, intersections)]) facile.constraint(scal_prod == bigsize) full_line(xs) full_line(ys) if facile.solve(xs + ys, heuristic=facile.Heuristic.Min_min): try: import matplotlib.pyplot as plt import matplotlib.cm as colormap from random import random as rand fig = plt.figure() ax = fig.gca() def fill_square(x, y, s): plt.fill([x, x, x+s, x+s], [y, y+s, y+s, y], color=colormap.Pastel1(rand())) fill_square(0, 0, bigsize) for (x, y, s) in zip(xs, ys, sizes): fill_square(x.value(), y.value(), s) ax.set_xlim((0, bigsize)) ax.set_ylim((0, bigsize)) ax.set_aspect(1) ax.set_xticks(range(bigsize + 1)) ax.set_yticks(range(bigsize + 1)) fig.set_size_inches(7, 7) ax.set_frame_on(False) plt.pause(10) except Exception as e: # if matplotlib fails for an unknown reason print (e) for (x, y, s) in zip(xs, ys, sizes): print (x.value(), y.value(), s)
def tile(sizes, bigsize): n = len(sizes) xs = [facile.variable(0, bigsize - sizes[i]) for i in range(n)] ys = [facile.variable(0, bigsize - sizes[i]) for i in range(n)] for i in range(n - 1): for j in range(i + 1, n): c_left = xs[j] + sizes[j] <= xs[i] # j on the left of i c_right = xs[j] >= xs[i] + sizes[i] # j on the right of i c_below = ys[j] + sizes[j] <= ys[i] # etc. c_above = ys[j] >= ys[i] + sizes[i] facile.constraint(c_left | c_right | c_below | c_above) # Redundant capacity constraint def full_line(xy): for i in range(bigsize): # equivalent to (xy[j] >= i - sizes[j] + 1) & (xy[j] <= i) intersections = \ [xy[j].in_interval(i - sizes[j] + 1, i) for j in range(n)] scal_prod = sum([s * k for s, k in zip(sizes, intersections)]) facile.constraint(scal_prod == bigsize) full_line(xs) full_line(ys) if facile.solve(xs + ys, heuristic=facile.Heuristic.Min_min): try: import matplotlib.pyplot as plt import matplotlib.cm as colormap from random import random as rand fig = plt.figure() ax = fig.gca() def fill_square(x, y, s): plt.fill([x, x, x + s, x + s], [y, y + s, y + s, y], color=colormap.Pastel1(rand())) fill_square(0, 0, bigsize) for (x, y, s) in zip(xs, ys, sizes): fill_square(x.value(), y.value(), s) ax.set_xlim((0, bigsize)) ax.set_ylim((0, bigsize)) ax.set_aspect(1) ax.set_xticks(range(bigsize + 1)) ax.set_yticks(range(bigsize + 1)) fig.set_size_inches(7, 7) ax.set_frame_on(False) plt.pause(10) except Exception as e: # if matplotlib fails for an unknown reason print(e) for (x, y, s) in zip(xs, ys, sizes): print(x.value(), y.value(), s)
def arithmetic(puzzle="SEND+MORE=MONEY", base=10): problem = re.split("[\s+=]", puzzle) # remove spaces problem = list(filter(lambda w: len(w) > 0, problem)) # letters letters = {l: fcl.variable(range(base)) for l in set("".join(problem))} # expressions expr_pb = [[letters[a] for a in word] for word in problem] words = [reduce(lambda a, b: 10 * a + b, word) for word in expr_pb] # constraints fcl.constraint(fcl.alldifferent(letters.values())) fcl.constraint(sum(words[:-1]) == words[-1]) for word in expr_pb: fcl.constraint(word[0] > 0) assert fcl.solve(letters.values()) # print solutions for word, numbers in zip(problem, expr_pb): strings = [str(n.value()) for n in numbers] print(word + " = " + "".join(strings))
def test_basic_array() -> None: var_list = [facile.variable(0, 1) for _ in range(5)] array = facile.array(var_list) x = facile.variable(range(10)) msg = "list indices must be integers or slices, not facile.core.Variable" with pytest.raises(TypeError, match=msg): facile.constraint(var_list[x] == 1) # type: ignore facile.constraint(array[x] == 1) facile.constraint(array.sum() == 1) facile.constraint(x == 1) solution = facile.solve([*array, x]) assert solution.solved assert array.value() == [0, 1, 0, 0, 0]
def arithmetic(puzzle="SEND+MORE=MONEY", base=10) -> None: problem = re.split(r"[\s+=]", puzzle) # remove spaces problem = list(filter(lambda w: len(w) > 0, problem)) letters = { letter: facile.variable(range(base)) for letter in set("".join(problem)) } # expressions expr_pb = [[letters[a] for a in word] for word in problem] def horner(a: Expression, b: Expression): return 10 * a + b words = [reduce(horner, word) for word in expr_pb] # constraints facile.constraint(facile.alldifferent(letters.values())) facile.constraint(facile.sum(words[:-1]) == words[-1]) for word in expr_pb: facile.constraint(word[0] > 0) assert facile.solve(letters.values()) # print solutions for word, numbers in zip(problem, expr_pb): strings = [str(n.value()) for n in numbers] print(f"{word} = {''.join(strings)}")
def golomb(n): # On peut majorer la taille de la règle par 2 ** n. En effet, si # ticks[i] vaut 2**i alors tous les ticks[i] - ticks[j] = 2**i - 2**j # = 2**j * (2**(i-j) - 1) qui sont tous différents. # On a donc au moins cette solution. n2 = 2 ** n ticks = [facile.variable(0, n2) for i in range(n)] # First tick at the start of the ruler facile.constraint(ticks[0] == 0) # Ticks are ordered for i in range(n-1): facile.constraint(ticks[i] < ticks[i+1]) # All distances distances = [] for i in range(n-1): for j in range(i + 1, n): distances.append(ticks[j] - ticks[i]) facile.constraint(facile.alldifferent(distances)) for d in distances: facile.constraint(d > 0) # Breaking the symmetry size = len(distances) facile.constraint(distances[size - 1] > distances[0]) return (facile.minimize(ticks, ticks[n-1])[1])
def test_nosolution() -> None: a, b, c = [facile.variable(0, 1) for _ in range(3)] facile.constraint(a != b) facile.constraint(b != c) facile.constraint(c != a) sol = facile.solve([a, b, c]) assert not sol.solved
def coins(values, maxval): """ Which coins do you need to give back change for any amount between 0 and maxval, using coins from values? """ # How many coin types nb_vals = len(values) nb_min_coins = [variable(0, maxval/values[i]) for i in range(nb_vals)] for val in range(maxval): # How many coins per type nb_coins = [variable(0, maxval/values[i]) for i in range(nb_vals)] mysum = sum([x[0] * x[1] for x in zip(values, nb_coins)]) constraint(mysum == val) for j in range(len(nb_coins)): constraint(nb_coins[j] <= nb_min_coins[j]) return minimize(nb_min_coins, sum(nb_min_coins))
def n_queens(n: int, *args, **kwargs) -> facile.Solution: queens = [facile.variable(range(n)) for i in range(n)] diag1 = [queens[i] + i for i in range(n)] diag2 = [queens[i] - i for i in range(n)] facile.constraint(facile.alldifferent(queens)) facile.constraint(facile.alldifferent(diag1)) facile.constraint(facile.alldifferent(diag2)) return facile.solve(queens, *args, **kwargs)
def test_invalid_array() -> None: n = 5 var_array = [[facile.variable(0, 1) for _ in range(n)] for _ in range(n)] np_array = np.array([var_array[0], var_array[1][1:]]) msg = ( "Only numpy arrays of variables, expressions, constraints and integers " "are accepted") with pytest.raises(TypeError, match=msg): _ = facile.array(np_array)
def coins(values, maxval): """ Which coins do you need to give back change for any amount between 0 and maxval, using coins from values? """ # How many coin types nb_vals = len(values) nb_min_coins = [variable(0, maxval / values[i]) for i in range(nb_vals)] for val in range(maxval): # How many coins per type nb_coins = [variable(0, maxval / values[i]) for i in range(nb_vals)] mysum = sum([x[0] * x[1] for x in zip(values, nb_coins)]) constraint(mysum == val) for j in range(len(nb_coins)): constraint(nb_coins[j] <= nb_min_coins[j]) return minimize(nb_min_coins, sum(nb_min_coins))
def test_magical() -> None: array = [facile.variable(range(10)) for i in range(10)] for i in range(10): sum_ = facile.sum(x == i for x in array) facile.constraint(sum_ == array[i]) solution = facile.solve(array) assert solution.solved assert solution.solution == [6, 2, 1, 0, 0, 0, 1, 0, 0, 0]
def test_domains() -> None: a = facile.variable(range(320)) b = facile.variable(range(160)) c = facile.variable(range(130)) d = facile.variable(range(130)) facile.constraint(a + b + c + d == 711) assert len(a.domain()) == 26 assert len(b.domain()) == 26 assert len(c.domain()) == 26 assert len(d.domain()) == 26 facile.constraint(a * b * c * d == 711000000) assert len(a.domain()) == 17 assert len(b.domain()) == 23 assert len(c.domain()) == 20 assert len(d.domain()) == 20 sol = facile.solve([a, b, c, d], backtrack=True) assert sol.solved assert sol.backtrack == 2
def test_2d_array() -> None: n = 5 # array = facile.Array.binary((n, n)) var_array = [[facile.variable(0, 1) for _ in range(n)] for _ in range(n)] array = facile.array(np.array(var_array)) for i in range(n): facile.constraint(array[:, i].sum() == 1) facile.constraint(array[i, :].sum() == 1) x, y = facile.variable(range(n)), facile.variable(range(n)) facile.constraint(array[x, y] == 1) # TODO redundant but necessary to test one of the arguments as a variable # facile.constraint(array[:, x].sum() == 1) sol = facile.solve([*array]) assert sol.solved sol = facile.solve([*array, x, y]) assert sol.solved *_, x_, y_ = sol.solution assert array[x_, y_].value() == 1
def n_queen(n): """Solves the n-queen problem. """ queens = [variable(0, n-1) for i in range(n)] diag1 = [queens[i] + i for i in range(n)] diag2 = [queens[i] - i for i in range(n)] constraint(alldifferent(queens)) constraint(alldifferent(diag1)) constraint(alldifferent(diag2)) if solve(queens): return [x.value() for x in queens] else: return None
def n_queen(n): """Solves the n-queen problem. """ queens = [variable(0, n - 1) for i in range(n)] diag1 = [queens[i] + i for i in range(n)] diag2 = [queens[i] - i for i in range(n)] constraint(alldifferent(queens)) constraint(alldifferent(diag1)) constraint(alldifferent(diag2)) if solve(queens): return [x.value() for x in queens] else: return None
def solver(cost, OWA): assert OWA in ['utilitarism', 'egalitarism', 'gini'] n_task = cost.shape[1] n_agent = cost.shape[0] boolean_variable = np.array([ facile.array([facile.variable([0, 1]) for i in range(n_task)]) for j in range(n_agent) ]) # Every task must be complete, by only one agent ! for j in range(n_task): facile.constraint(sum(boolean_variable[:, j]) == 1) how_does_it_cost = facile.array([ np.matmul(cost[i, :], facile.array(boolean_variable[i, :])) for i in range(n_agent) ]) if OWA == 'utilitarism': weights = np.array([1 for i in range(n_agent)]) elif OWA == 'egalitarism': # as we only have the .sort() method for desutilities, weights must be in reverse order because the 'desutilities' should be sorted in descending order weights = np.array([0 for i in range(n_agent)]) weights[-1] = 1 else: # as we only have the .sort() method for desutilities, weights must be in reverse order because the 'desutilities' should be sorted in descending order weights = np.flip( np.array([2 * (n_agent - i) + 1 for i in range(1, n_agent + 1)])) print(weights) to_minimize = np.matmul(weights, how_does_it_cost.sort()) vars = [] for i in range(n_agent): for j in range(n_task): vars.append(boolean_variable[i, j]) res = np.array(facile.minimize(vars, to_minimize).solution) boolean_res = res > 0 tasks = np.array([list(string.ascii_lowercase)[0:n_task]]) for i in range(n_agent): boolean_res[i:i + n_task] print(i + 1, ' does : ', tasks[:, boolean_res[n_task * i:n_task * (i + 1)]][0])
def n_queen(n): """Solves the n-queen problem. """ queens = [variable(range(n)) for i in range(n)] # prepare for animation def on_bt(nb_bt): for i, q in enumerate(queens): print(i, q.domain()) constraint(alldifferent(queens)) constraint(alldifferent(queens[i] - i for i in range(n))) constraint(alldifferent(queens[i] + i for i in range(n))) return solve(queens, strategy=queen_strategy, backtrack=True)
def lazy_n_queens(n: int, *args, **kwargs) -> facile.Solution: queens = [facile.variable(range(n)) for i in range(n)] diag1 = [queens[i] + i for i in range(n)] diag2 = [queens[i] - i for i in range(n)] # facile.constraint(facile.alldifferent(queens)) for i, q1 in enumerate(queens): for q2 in queens[i + 1:]: facile.constraint(q1 != q2) # facile.constraint(facile.alldifferent(diag1)) for i, q1 in enumerate(diag1): for q2 in diag1[i + 1:]: facile.constraint(q1 != q2) # facile.constraint(facile.alldifferent(diag2)) for i, q1 in enumerate(diag2): for q2 in diag2[i + 1:]: facile.constraint(q1 != q2) return facile.solve(queens, *args, **kwargs)
# 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? # # Originally from F. J. Pelletier: # Seventy-five problems for testing automatic theorem provers. # Journal of Automated Reasoning, 2: 216, 1986. from facile import Array, constraint, solve, sum, variable n = 3 agatha, butler, charles = 0, 1, 2 killer = variable(range(3)) victim = variable(range(3)) hates = Array.binary((n, n)) richer = Array.binary((n, n)) # 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. constraint(hates[killer, victim] == 1) constraint(richer[killer, victim] == 0) # No one is richer than him-/herself for i in range(n): constraint(richer[i, i] == 0)
import facile # Magical sequence! # The value inside array[i] is equal to the number of i in array array = [facile.variable(0,10) for i in range(10)] for i in range(10): facile.constraint(sum([x == i for x in array]) == array[i]) if facile.solve(array): print ([v.value() for v in array])
# flake8: noqa: E226 from facile import constraint, solve, sum, variable # Number of buckets nb = 3 # Number of steps (let's say we know... :p) steps = 8 # The capacity of each bucket capacity = [8, 5, 3] buckets = [ [variable(range(capacity[b] + 1)) for b in range(nb)] for i in range(steps) ] constraint(buckets[0][0] == 8) constraint(buckets[0][1] == 0) constraint(buckets[0][2] == 0) constraint(buckets[steps - 1][0] == 4) constraint(buckets[steps - 1][1] == 4) constraint(buckets[steps - 1][2] == 0) for i in range(steps - 1): # we change the contents of two buckets at a time sum_buckets = sum([buckets[i][b] != buckets[i + 1][b] for b in range(nb)]) constraint(sum_buckets == 2) # we play with a constant amount of water sum_water = sum([buckets[i][b] for b in range(nb)]) constraint(sum_water == 8) for b1 in range(nb):
# -*- coding: utf-8 -*- """ Basic examples of CSP problems: - a ≠ b - alldifferent(a, b, c) and a + b <= 2c """ from facile import variable, constraint, solve, alldifferent a = variable(0, 1) b = variable(0, 1) constraint(a != b) if solve([a, b]): print("Solution found a=%d and b=%d" % (a.value(), b.value())) a = variable(0, 2) b = variable(0, 2) c = variable(0, 2) constraint(alldifferent([a, b, c])) constraint(a + b <= 2 * c) if solve([a, b, c]): print("Solution found a=%d, b=%d and c=%d" % (a.value(), b.value(), c.value()))
# Variables # There is a risk of integer overflow when computing a*b*c*d # We need small domains... import facile a = facile.variable(range(0, 321)) b = facile.variable(range(0, 161)) c = facile.variable(range(0, 131)) d = facile.variable(range(0, 131)) # Constraints # The problem facile.constraint(a + b + c + d == 711) print("Domains after posting the sum constraint") for x in [a, b, c, d]: domain = x.domain() print(" {!r} (size {})".format(domain, len(domain))) facile.constraint(a * b * c * d == 711000000) print("\nDomains after posting the mul constraint") for x in [a, b, c, d]: domain = x.domain() print(" {!r} (size {})".format(domain, len(domain))) print() # Resolution sol = facile.solve([a, b, c, d], backtrack=True)
from facile import alldifferent, constraint, solve, variable colors = [variable(range(1, 6)) for i in range(5)] red, green, yellow, blue, ivory = colors people = [variable(range(1, 6)) for i in range(5)] englishman, spaniard, japanese, ukrainian, norwegian = people names = ["Englishman", "Spaniard", "Japanese", "Ukrainian", "Norwegian"] animals = [variable(range(1, 6)) for i in range(5)] dog, snails, fox, zebra, horse = animals drinks = [variable(range(1, 6)) for i in range(5)] tea, coffee, water, milk, fruit_juice = drinks cigarettes = [variable(range(1, 6)) for i in range(5)] old_gold, kools, chesterfields, lucky_strike, parliaments = cigarettes constraint(alldifferent(colors)) constraint(alldifferent(people)) constraint(alldifferent(animals)) constraint(alldifferent(drinks)) constraint(alldifferent(cigarettes)) constraint(englishman == red) constraint(spaniard == dog) constraint(coffee == green) constraint(ukrainian == tea) constraint(green == ivory + 1) constraint(old_gold == snails)
def tiles(sizes, bigsize): n = len(sizes) xs = [variable(range(bigsize - sizes[i] + 1)) for i in range(n)] ys = [variable(range(bigsize - sizes[i] + 1)) for i in range(n)] for i in range(n - 1): for j in range(i + 1, n): c_left = xs[j] + sizes[j] <= xs[i] # j on the left of i c_right = xs[j] >= xs[i] + sizes[i] # j on the right of i c_below = ys[j] + sizes[j] <= ys[i] # etc. c_above = ys[j] >= ys[i] + sizes[i] constraint(c_left | c_right | c_below | c_above) # Redundant capacity constraint def full_line(xy): for i in range(bigsize): # equivalent to (xy[j] >= i - sizes[j] + 1) & (xy[j] <= i) intersections = [ xy[j].in_interval(i - sizes[j] + 1, i) for j in range(n) ] scal_prod = sum([s * k for s, k in zip(sizes, intersections)]) constraint(scal_prod == bigsize) full_line(xs) full_line(ys) gx = Goal.forall(xs, assign="assign", strategy="min_min") gy = Goal.forall(ys, assign="assign", strategy="min_min") # Now the proper resolution process solution = solve(gx & gy, backtrack=True) print(solution) try: import matplotlib.pyplot as plt # type: ignore fig, ax = plt.subplots(figsize=(7, 7)) def fill_square(x, y, s): plt.fill( [x, x, x + s, x + s], [y, y + s, y + s, y], color=plt.get_cmap("tab20")(random()), ) fill_square(0, 0, bigsize) for (x, y, s) in zip(xs, ys, sizes): fill_square(x.value(), y.value(), s) ax.set_xlim((0, bigsize)) ax.set_ylim((0, bigsize)) ax.set_aspect(1) ax.set_xticks(range(bigsize + 1)) ax.set_yticks(range(bigsize + 1)) ax.set_frame_on(False) plt.pause(10) except ImportError as e: # if matplotlib fails for an unknown reason print(e) for (x, y, s) in zip(xs, ys, sizes): print(x.value(), y.value(), s)
# -*- coding: utf-8 -*- """ Find four numbers such that their sum is 711 and their product is 711000000 """ from facile import variable, constraint, solve a = variable(0, 330) b = variable(0, 160) c = variable(0, 140) d = variable(0, 140) constraint(a + b + c + d == 711) constraint(a * b * c * d == 711000000) if solve([a, b, c, d]): [va, vb, vc, vd] = [x.value() for x in [a, b, c, d]] print ("Solution found a=%d, b=%d, c=%d, d=%d" % (va, vb, vc, vd)) else: print ("No solution found")
# -*- coding: utf-8 -*- """ Basic examples of CSP problems: - a ≠ b - alldifferent(a, b, c) and a + b <= 2c """ from facile import variable, constraint, solve, alldifferent a = variable(0, 1) b = variable(0, 1) constraint(a != b) if solve([a, b]): print ("Solution found a=%d and b=%d" % (a.value(), b.value())) a = variable(0, 2) b = variable(0, 2) c = variable(0, 2) constraint(alldifferent([a, b, c])) constraint(a + b <= 2 * c) if solve([a, b, c]): print ("Solution found a=%d, b=%d and c=%d" % (a.value(), b.value(), c.value()))
# -*- coding: utf-8 -*- """ Find four numbers such that their sum is 711 and their product is 711000000 """ from facile import variable, constraint, solve a = variable(range(0, 330)) b = variable(range(0, 160)) c = variable(range(0, 140)) d = variable(range(0, 140)) constraint(a + b + c + d == 711) constraint(a * b * c * d == 711000000) sol = solve([a, b, c, d]) print("Solution found a={}, b={}, c={}, d={}".format(*sol.solution))
# https://github.com/google/or-tools/.../examples/python/furniture_moving.py # Moving furnitures (scheduling) problem in Google CP Solver. # Marriott & Stukey: 'Programming with constraints', page 112f # The model implements an experimental decomposition of the # global constraint cumulative. import facile n = 4 duration = [30, 10, 15, 15] demand = [3, 1, 3, 2] upper_limit = 160 start_times = [facile.variable(range(upper_limit)) for i in range(n)] end_times = [facile.variable(range(2 * upper_limit)) for i in range(n)] end_time = facile.variable(range(2 * upper_limit)) n_resources = facile.variable(range(11)) for i in range(n): facile.constraint(end_times[i] == start_times[i] + duration[i]) facile.constraint(end_time == facile.array(end_times).max()) # detail here! def cumulative(s, d, r, b): tasks = [i for i in range(len(s)) if r[i] > 0 and d[i] > 0] times_min = min([s[i].domain()[0] for i in tasks]) times_max = max([s[i].domain()[-1] + max(d) for i in tasks]) for t in range(times_min, times_max + 1):
import facile # The list comprehension mechanism is always helpful! [s, e, n, d, m, o, r, y] = [facile.variable(range(10)) for i in range(8)] # A shortcut letters = [s, e, n, d, m, o, r, y] # Retenues [c0, c1, c2] = [facile.variable([0, 1]) for i in range(3)] # Constraints # facile.constraint(s > 0) # facile.constraint(m > 0) facile.constraint(facile.alldifferent(letters)) facile.constraint(d + e == y + 10 * c0) facile.constraint(c0 + n + r == e + 10 * c1) facile.constraint(c1 + e + o == n + 10 * c2) facile.constraint(c2 + s + m == o + 10 * m) if facile.solve(letters): [vs, ve, vn, vd, vm, vo, vr, vy] = [x.value() for x in letters] print("Solution found :") print print(" %d%d%d%d" % (vs, ve, vn, vd)) print("+ %d%d%d%d" % (vm, vo, vr, ve)) print("------") print(" %d%d%d%d%d" % (vm, vo, vn, ve, vy)) else: print("No solution found")
# # The formulation is generalized to any number of golfers, groups and weeks. nb_groups = 5 nb_golfers = 15 size_group = 3 assert (size_group * nb_groups == nb_golfers) nb_weeks = 5 # An array of nb_weeks * nb_golfers decision variables to choose the group of # every golfer every week groups = [ facile.array( [facile.variable(0, nb_groups-1) for i in range(nb_golfers)] ) for j in range(nb_weeks)] # For each week, exactly size_group golfers in each group: for i in range(nb_weeks): # [1] Use a Sorting Constraint (redundant with [2]) s = groups[i].sort() for j in range(nb_golfers): facile.constraint(s[j] == j//size_group) # [2] Use a Global Cardinality Constraint (redundant with [1]) gcc = groups[i].gcc([(size_group, i) for i in range(nb_groups)]) facile.constraint(gcc) # Two golfers do not play in the same group more than once
def test_overconstrained() -> None: a, b, c = [facile.variable(0, 1) for _ in range(3)] with pytest.raises(ValueError, match="The problem is overconstrained"): facile.constraint(facile.alldifferent([a, b, c]))
import facile # Magical sequence! # The value inside array[i] is equal to the number of i in array array = [facile.variable(0, 10) for i in range(10)] for i in range(10): facile.constraint(sum([x == i for x in array]) == array[i]) if facile.solve(array): print([v.value() for v in array])
def test_solution() -> None: a = facile.variable([0, 1]) b = facile.variable([0, 1]) facile.constraint(a != b) sol = facile.solve([a, b]) assert sol.solved
import facile import functools # The list comprehension mechanism is always helpful! [s, e, n, d, m, o, r, y] = [facile.variable(range(10)) for i in range(8)] # A shortcut letters = [s, e, n, d, m, o, r, y] # Constraints facile.constraint(s > 0) facile.constraint(m > 0) facile.constraint(facile.alldifferent(letters)) send = functools.reduce(lambda x, y: 10 * x + y, [s, e, n, d]) more = functools.reduce(lambda x, y: 10 * x + y, [m, o, r, e]) money = functools.reduce(lambda x, y: 10 * x + y, [m, o, n, e, y]) facile.constraint(send + more == money) if facile.solve(letters): [vs, ve, vn, vd, vm, vo, vr, vy] = [x.value() for x in letters] print("Solution found :") print print(" %d%d%d%d" % (vs, ve, vn, vd)) print("+ %d%d%d%d" % (vm, vo, vr, ve)) print("------") print(" %d%d%d%d%d" % (vm, vo, vn, ve, vy)) else: print("No solution found")
from facile import variable, constraint, solve # Number of buckets nb = 3 # Number of steps (let's say we know... :p) steps = 8 # The capacity of each bucket capacity = [8, 5, 3] buckets = [ [variable(0, capacity[b]) for b in range(nb)] for i in range(steps)] constraint(buckets[0][0] == 8) constraint(buckets[0][1] == 0) constraint(buckets[0][2] == 0) constraint(buckets[steps - 1][0] == 4) constraint(buckets[steps - 1][1] == 4) constraint(buckets[steps - 1][2] == 0) for i in range(steps - 1): # we change the contents of two buckets at a time constraint( sum([buckets[i][b] != buckets[i+1][b] for b in range(nb)]) == 2) # we play with a constant amount of water constraint(sum([buckets[i][b] for b in range(nb)]) == 8) for b1 in range(nb): for b2 in range(b1): constraint( # either the content of the bucket does not change (buckets[i][b1] == buckets[i+1][b1]) | (buckets[i][b2] == buckets[i+1][b2]) | # or the bucket ends up empty or full